Showing posts with label iOS 10. Show all posts
Showing posts with label iOS 10. Show all posts

Saturday, July 13, 2019

Swift - Notify ViewController from AppDelegate On FCM / APN

Hello,

Recently I tried  my hands on iOS app after long time as I had to convert one of our hybrid app to native app on urgent basis. There I had webview where I was loading my html app. On FCM message receieved we need to send URL received in FCM to webview and load the URL.

So here in this blog I will mention the procedure for it.

First of all in AppDelegate we have didReceiveRemoteNotification method. There we will create notification for it. Here is the code.


let url = dict
serverURL = url;
let notificationName = Notification.Name("updateWebView")
NotificationCenter.default.post(name: notificationName, object: nil)

Now in the ViewController where you want to make updates, subscribe to this notification in viewDidLoad function.

override func viewDidLoad() {
  let notificationName = Notification.Name("updateWebView")
  NotificationCenter.default.addObserver(self, selector:            #selector(ViewController.updateWebView), name: notificationName, object: nil)
}

And then add function in ViewController.

@objc func updateWebView() {
  let appDelegate = UIApplication.shared.delegate as! AppDelegate
  let serverURL = appDelegate.serverURL
        
  guard let url = URL(string: serverURL!) else {
     print("Invalid URL")
     return
  }
        
  let request = URLRequest(url: url)
  webView.load(request)
}

That's it and now you will have data passed from AppDelegate to ViewController. This method you can pass any kind of data from FCM to respective ViewController.

Hope this helps you.



Saturday, January 21, 2017

Xcode 8.2 Simulator Crash When Save Screen Shot - Alternate way to take Screenshot of iPhone simulator

I don't know what went wrong with my Xcode. Recently I was publishing an app in iTunes connect and for that I needed iPhone 7 screen shot. I opened simulator and run and app and tried to capture screenshot with Command + S and it crashed the simulator with following error and screen shot file was empty.



It shows some error related to some library of SwiftFoundation. I was not sure about this error. So first thing what I did is report it to apple and then tried few things like. Restarting simulator couple of times and restarting Xcode couple of times. But it didn't work. So may be it's related to SDK update. I updated the latest SDK but still it was not working. So at last I give it to Apple to solve the problem but I needed that screen shot. So here is alternate way to take Screenshot of iPhone simulator.

With simulator running. Select Go to Edit menu and Select Copy Screen.



This will copy current screen of simulator. Now open the preview and go to File and Select New From Clipboard.



And it will give you new image with copied screen of your simulator, now save it and use it with Preview. Hope this helps you.

Saturday, January 14, 2017

AVCaptureVideoPreviewLayer Black Screen. AVfoundation Black Screen on Record

Recently in one of my project we used AVFoundation to record video. In some of the iOS devices, we were getting issue that on start recording, it shows black screen and video is not recorded. After some investigation I found out that it's because user has manually revoked camera access from settings so it was not woking. So to solve this issue, you must check if the permission is there or not. If not first request permission and if it's denied, so message to user.

So here is the function you should use. You should call this function, before you start recording and check if there is necessary permission.

- (void)requestCameraPermissionsIfNeeded {
   
    NSLog(@"requestCameraPermissionsIfNeeded");
    // check camera authorization status
    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    switch (authStatus) {
        case AVAuthorizationStatusAuthorized: { // camera authorized
            NSLog(@"requestCameraPermissionsIfNeeded camera authorized");
            // do camera intensive stuff
        }
            break;
        case AVAuthorizationStatusNotDetermined: { // request authorization
            NSLog(@"requestCameraPermissionsIfNeeded have to ask user again");
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
                dispatch_async(dispatch_get_main_queue(), ^{
                   
                    if(granted) {
                        // do camera intensive stuff
                    } else {
                       
                        NSLog(@"STOP RECORDING");
                        WeAreRecording = NO;
                        ShareVideo = YES;
                        [MovieFileOutput stopRecording];
                        //Prompt message to user.
                    }
                });
            }];
        }
            break;
        case AVAuthorizationStatusRestricted:{
            NSLog(@"STOP RECORDING");
            WeAreRecording = NO;
            ShareVideo = YES;
            [MovieFileOutput stopRecording];
            //Prompt message to user.
        }
           
        case AVAuthorizationStatusDenied: {
            NSLog(@"STOP RECORDING");
            WeAreRecording = NO;
            ShareVideo = YES;
            [MovieFileOutput stopRecording];
            //Prompt message to user.
            dispatch_async(dispatch_get_main_queue(), ^{
            });
        }
            break;
        default:
            break;
    }
}


This will help you in identifying issue and display proper message to user.

Sunday, January 8, 2017

Cordova FacebookConnect Plugin Not Working in iOS 9 and iOS 10

Recently in one of my Cordova project we have Connect with Facebook functionality where I have faced certain issues to make it working in iOS 9 and iOS 10 so in this blog I am going to explain those issues and how to resolve it.

1) Which Plugin to Use

There are two plugins if you search for Cordova Facebook Plugin. Following are two links.

GitHub - Wizcorp/phonegap-facebook-plugin

GitHub - jeduan/cordova-plugin-facebook4

The first plugin is older one and it works good till iOS 7 and iOS 8 and cordova 4.0 iOS but it does not work for iOS 10 and Cordova 6.0. If you use that plugin you will usually get issues like you will not be redirected to Facebook in mobile safari or after authentication, it come back to your app and nothing happen.

So my recommendation is to go for second Plugin, which works fine with latest cordova and iOS 9 and iOS 10.

2) After authentication, it come back to your app and nothing happen.

This issue is observed in iOS 9 and iOS 10 for both the plugins. That's because of LSApplicationQueriesSchemes introduced in iOS 9 on words.

There are two URL-related methods available to apps on iOS that are effected: canOpenURL and openURL. These are not new methods and the methods themselves are not changing. As you might expect from the names, “canOpenURL” returns a yes or no answer after checking if there is any apps installed on the device that know how to handle a given URL. “openURL” is used to actually launch the URL, which will typically leave the app and open the URL in another app.

Up until iOS 9, apps have been able to call these methods on any arbitrary URLs. Starting on iOS 9, apps will have to declare what URL schemes they would like to be able to check for and open in the configuration files of the app as it is submitted to Apple. This is essentially a whitelist that can only be changed or added to by submitting an update to Apple.

So you have to WhiteList all the Facebook App Schemes in your info.plist file. Following are schemes you have to add.

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>fbapi</string>
    <string>fbapi20130214</string>
    <string>fbapi20130410</string>
    <string>fbapi20130702</string>
    <string>fbapi20131010</string>
    <string>fbapi20131219</string>  
    <string>fbapi20140410</string>
    <string>fbapi20140116</string>
    <string>fbapi20150313</string>
    <string>fbapi20150629</string>
    <string>fbapi20160328</string>
    <string>fbauth</string>
    <string>fbauth2</string>
    <string>fb-messenger-api20140430</string>
</array>

3) There was an error making the graph call

I spent almost an hour to solve this issue. Everything was configured but when I try to make graph API call to get basic profile, it fails every time and the reason behind this was in graph api call IO have space after each field.

facebookConnectPlugin.api("me/?fields=id, first_name, last_name, email",["public_profile"],
function (result) {
},
function (error) {
});

As you can see above there was a space after each field. So to make it working. Remove that space. It does not affect in Android but it does not work in iOS.

facebookConnectPlugin.api("me/?fields=id,first_name,last_name,email",["public_profile"],
function (result) {
},
function (error) {
});

Monday, December 19, 2016

Objective C - Record Video With AVCaptureSession

Hello,

In this blog I am going to explain how to record video with AVCaptureSession in your iOS application.

First of all add following import statements in your view controller header file.

#import <Foundation/Foundation.h>
#import <CoreMedia/CoreMedia.h>
#import <AVFoundation/AVFoundation.h>
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>

Now we will have to set preview layer for the recording in our view and also we will need input device and output file location. Also we need to add AVCaptureFileOutputRecordingDelegate to have notifications of events like recording stop.

Implement this delegate in your header file.

@interface MainViewController : CDVViewController
{
    BOOL WeAreRecording;
    BOOL ShareVideo;
    AVCaptureSession *CaptureSession;
    AVCaptureMovieFileOutput *MovieFileOutput;
    AVCaptureDeviceInput *VideoInputDevice;
}

Now we will set preview layer and init AVCaptureSession in viewDidLoad and set input and output.

CaptureSession = [[AVCaptureSession alloc] init];
AVCaptureDevice *VideoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
NSError *error = nil;
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioCaptureDevice error:&error];
if (audioInput)
{
[CaptureSession addInput:audioInput];
}

[self setPreviewLayer:[[AVCaptureVideoPreviewLayer alloc] initWithSession:CaptureSession]];
PreviewLayer.orientation = AVCaptureVideoOrientationLandscapeRight;
[[self PreviewLayer] setVideoGravity:AVLayerVideoGravityResizeAspectFill];

Now we will setup output file and video recording settings and image quality.

MovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
Float64 TotalSeconds = 60;
int32_t preferredTimeScale = 30;
CMTime maxDuration = CMTimeMakeWithSeconds(TotalSeconds, preferredTimeScale);
MovieFileOutput.maxRecordedDuration = maxDuration;
MovieFileOutput.minFreeDiskSpaceLimit = 1024 * 1024;
   
if ([CaptureSession canAddOutput:MovieFileOutput])
    [CaptureSession addOutput:MovieFileOutput];
   
[self CameraSetOutputProperties];

[CaptureSession setSessionPreset:AVCaptureSessionPresetMedium];
if ([CaptureSession canSetSessionPreset:AVCaptureSessionPreset640x480])
    [CaptureSession setSessionPreset:AVCaptureSessionPreset640x480];
   
CGRect layerRect = [[[self view] layer] bounds];
CGRect viewBoundsPreview = [self.webView bounds];
viewBoundsPreview.origin.y = 20;
viewBoundsPreview.size.height = viewBoundsPreview.size.height - 40;
[PreviewLayer setBounds:viewBoundsPreview];
[PreviewLayer setPosition:CGPointMake(CGRectGetMidX(layerRect),
 CGRectGetMidY(layerRect))];

UIView *CameraView = [[UIView alloc] init];
[[self view] addSubview:CameraView];
[self.view sendSubviewToBack:CameraView];
[[CameraView layer] addSublayer:PreviewLayer];
[CaptureSession startRunning];

Now capture session is running, we have to start and stop recording.

To start recording, add following code to your handler.

NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
NSNumber *timeStampObj = [NSNumber numberWithInteger:timeStamp];
NSString* fileName = [timeStampObj stringValue];
fileName = [fileName stringByAppendingString:@".mov"];
NSString *outputPath = [[NSString alloc] initWithFormat:@"%@%@", NSTemporaryDirectory(), fileName];
NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:outputPath];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:outputPath])
{
NSError *error;
if ([fileManager removeItemAtPath:outputPath error:&error] == NO)
{
//Error - handle
}
}
//Start recording
[MovieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];


Above code will start recording. Add following code to stop recording.

[MovieFileOutput stopRecording];

This will stop recording and save video to Photos library.

Friday, December 16, 2016

Objective C - Play Video From Application Temp Folder

Recently in one of my iOS project , there was requirement to play Video stored in temporary folder of Application data. After some hours of struggle I managed to get it working.

So the problem I was facing is I have absolute URL of the video that I was trying to play in MPMoviePlayerController but it was not working as the player was displayed for couple of seconds and it's dismissed automatically and there was a black screen.

So after sometime I found out that MPMoviePlayerController is deprecated, instead of it we shall use AVPlayer and that too was not working if I give absolute path to initialize a player. So first of all I just extracted file name fro the absolute path will following code.

NSRange range = [filePath rangeOfString:@"/" options:NSBackwardsSearch];
NSUInteger index = range.location;      
filename = [filePath substringFromIndex:index+1];

Now we will initialize player. Please note you have to import AVKIt first in your header file.

#import <AVKit/AVKit.h>

Now initialize player.

NSString *outputPath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
AVAsset *asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:outputPath]];
AVPlayer *_avPlayer = [[AVPlayer alloc]initWithPlayerItem:[[AVPlayerItem alloc]initWithAsset:asset]];

movieLayer = [AVPlayerLayer playerLayerWithPlayer:_avPlayer];
movieLayer.frame = self.view.bounds;
[self.view.layer addSublayer:movieLayer];

So player will be added as sublayer on the view so we have to dismiss it when video finished playing.

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(videoDidFinish:)
name:AVPlayerItemDidPlayToEndTimeNotification
  object:[_avPlayer currentItem]];

And add callback function to remove player layer.

- (void)videoDidFinish:(id)notification
{
    NSLog(@"finsihed");
    [movieLayer removeFromSuperlayer];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}


That's it. Hope this will help you.

FilePicker Cordova iOS Plugin - Get Files From Photos

Hello,

Recently in one of my iOS project we have requirement to let user browse and select files. So we needed FilePicker plugin for iOS. It should also allow user to browse through document providers like iCloud drive, Dropbox or Google drive.


So after searching for the plugin I found following plugin which works fine for iCloud drive.


https://github.com/jcesarmobile/FilePicker-Phonegap-iOS-Plugin


I would like to thank developer of above plugin as I just added more code to it to fulfill my requirement.

But my other requirements were not fulfilled to pick up photos and videos from saved photos album so I made some changes in this plugin. Here in this blog I will explain how to do this.

First of all install above plugin through command line and open your project in Xcode and open file

Plugins ==> FilePicker.h and Plugins ==> FilePicker.m

This plugin shows pop over menu with all available document providers so first we have to add option to browse photos and videos.

Open FilePicker.h file and add following import statement.

#import

And add following delegates.

@interface FilePicker : CDVPlugin

Now Open FilePicker.m file and find displayDocumentPicker and add following code to it.

[importMenu addOptionWithTitle:@"Photos & Videos" image:nil order:UIDocumentMenuOrderFirst handler:^{
        
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
imagePickerController.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:imagePickerController.sourceType];
imagePickerController.allowsEditing = NO;
imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh;
imagePickerController.delegate = self;
[self.viewController presentViewController:imagePickerController animated:YES completion:nil];

}];


This will add menu and we set delegate to self so now we have to callback functions. Add following function in the file.

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
    if ([mediaType isEqualToString:@"public.image"]){
        NSData *imageData = UIImagePNGRepresentation((UIImage*) [info objectForKey:UIImagePickerControllerOriginalImage]);
        NSString* size = [NSString stringWithFormat:@"%li",  (unsigned long)[imageData length]];
        NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
        NSNumber *timeStampObj = [NSNumber numberWithInteger:timeStamp];
        NSString* fileName = [timeStampObj stringValue];
        
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0];
        NSString *imagePath =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png",fileName]];
        if (![imageData writeToFile:imagePath atomically:NO])
        {
            //send failure response;
            self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Failed to cache image data to disk"];
            [self.pluginResult setKeepCallbackAsBool:NO];
            [self.commandDelegate sendPluginResult:self.pluginResult callbackId:self.command.callbackId];
        }
        else
        {
            NSArray *arr = @[
                             @{@"path": imagePath, @"size": size}
                             ];
            
            self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:arr];
            [self.pluginResult setKeepCallbackAsBool:NO];
            [self.commandDelegate sendPluginResult:self.pluginResult callbackId:self.command.callbackId];
        }
    }
    else if ([mediaType isEqualToString:@"public.movie"]){
        NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];
        NSString* path = [[videoURL absoluteString] substringFromIndex:7];
        NSData *data = [NSData dataWithContentsOfURL:videoURL];
        NSString* size = [NSString stringWithFormat:@"%li",  (unsigned long)[data length]];
        NSArray *arr = @[
                         @{@"path": path, @"size": size}
                         ];
        
        self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:arr];
        [self.pluginResult setKeepCallbackAsBool:NO];
        [self.commandDelegate sendPluginResult:self.pluginResult callbackId:self.command.callbackId];
    }
    [picker dismissViewControllerAnimated:YES completion:NULL];
}

So as you can see in above code we are checking if picked media is image, then first we have to move application temp storage as iOS does not allow you to access assets directly from photos so we are making a copy with following code.

NSData *imageData = UIImagePNGRepresentation((UIImage*) [info objectForKey:UIImagePickerControllerOriginalImage]);
NSString* size = [NSString stringWithFormat:@"%li",  (unsigned long)[imageData length]];
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
NSNumber *timeStampObj = [NSNumber numberWithInteger:timeStamp];
NSString* fileName = [timeStampObj stringValue];

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *imagePath =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png",fileName]];
if (![imageData writeToFile:imagePath atomically:NO])
{
}
else
{
}


And for the videos we are sharing absolute URL to result callback. Also the plugin result is now array with path and size attribute. So we have to change the code of plugin to send same result for other document providers. Find out following function in FilePicker.m file.

- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url

And replace it with following function.

- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url {
    
    [url startAccessingSecurityScopedResource];
    __block NSData *pdfData = nil;
    
    NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
    __block NSError *error;
    [coordinator coordinateReadingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) {
        pdfData = [NSData dataWithContentsOfURL:newURL];
        NSString* size = [NSString stringWithFormat:@"%li",  (unsigned long)[pdfData length]];
        NSArray *arr = @[
                         @{@"path": [url path], @"size": size}
                         ];
        
        self.pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:arr];
        [self.pluginResult setKeepCallbackAsBool:NO];
        [self.commandDelegate sendPluginResult:self.pluginResult callbackId:self.command.callbackId];
    }];
    [url stopAccessingSecurityScopedResource];
    
}

So in JavaScript, following code should work.

FilePicker.pickFile(function(obj) {
alert(obj[0].path);
alert(obj[0].size);
}

Hope this helps you.

Saturday, December 10, 2016

Cordova Application Hanging During Startup on iOS 10

Hello,

If you have any cordova application in iTunes, you may have faced this issue since launch of iOS 10,. Either your app hangs at Start up or it will hang in case when there is a use of any plugin, like camera or location or any other native features. 

This is because of content security policy. iOS 10 needs content security policy where you have to mention what types of content you will allow to load.

As you have notice cordova plugins are invoked gap:// and in iOS 10 it's not allowed by default so you have to mention this in content security policy. 

Add following line in head section of your index.html file.

<meta http-equiv="Content-Security-Policy" content="media-src *; img-src * data:; font-src * data:; default-src  * gap:; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'">


As you can see we have added gap: in allowed content src along with other source, now your app will work normally in iOS 10.

Hope this helps you.