Unable to present document interaction controller - ios

I recently submited an app and it got rejected due to the isse:
the share button does not function, when a user presses it no action
occurs
It was working before I submit it to appstore. But now it's not.
I am unable to present the document interaction controller.
HEre is what I had working before:
UIAlertAction* share = [UIAlertAction
actionWithTitle:#"Share"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[self shareImage];
[alert dismissViewControllerAnimated:YES completion:nil];
}];
After executing this I got the warning:
_bsmacherror (os/kern) invalid capability (20)
_bsmacherror (os/kern) invalid name (15)
then I put the shareImage code on main thread:
dispatch_async(dispatch_get_main_queue(), ^{
[self shareImage];
});
The warning is disappeared......But it still doesnt open the document interac controller
And the code to open doc interac controller is:
-(void)shareImage{
UIButton *button = (UIButton *)nil;
NSURL *URL = [NSURL fileURLWithPath:_savedImagePath];
if (URL) {
// Initialize Document Interaction Controller
self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:URL];
// Configure Document Interaction Controller
[self.documentInteractionController setDelegate:self];
// Present Open In Menu
[self.documentInteractionController presentOpenInMenuFromRect:[button frame] inView:self.view animated:YES];
}
}
I set the UIDocumentInteractionControllerDelegate and I checked that image exists at the _savedImagePath .
Is there any error handling method for doc interac controller ?
How do I solve the issue ?

Your problem appears to be due to button being nil and trying to present the controller from the nil button's frame. That gives you a frame of CGRectZero. Why are you doing that?
Show the controller from a valid frame or from a UIBarButtonItem.

Related

MPMediaPickerController didPickMediaItems returns MPModelObjectMediaItem

I am having a super strange issue. I am using iOS13 MPMediaPickerController to get some songs from the users apple music library in order to play it via the [MPMusicPlayerController applicationMusicPlayer].
Now with some non-downloaded songs from some playlists that I have added to my library the delegate function
- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
returns for
mediaItemCollection.items.firstObject
an object of the type MPModelObjectMediaItem which cannot be played by the player. (I can also not cast to that type because it's unknown. Also there is no documentation out there for this type. It seems to be a private class inside the mediaplayer framework.)
I am expecting the type MPConcreteMediaItem which is incoming for all downloaded (and all other cloud items that are not in playlists)
The picker is configured like this
musicPicker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic];
musicPicker.showsCloudItems = true;
musicPicker.showsItemsWithProtectedAssets = true;
musicPicker.delegate = self;
musicPicker.allowsPickingMultipleItems = false;
Any tips whats going on there?
Well, it seems that items that you load into your own application cannot be properly loaded if you haven't added them into your music library. (iOS13.3)
I am using this workaround:
- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
assert(mediaItemCollection.count == 1);
// this check is done because it seems that in the following scenario you dont get a
// MPConcreteMediaItem which can be played. Instead you get a MPModelObjectMediaItem
// this happens when an item was selected that is inside a playlist, but does not belong
// to your library (nor is it downloaded to your device)
if(mediaItemCollection.items.firstObject.persistentID <= 0)
{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Song Not Added")
message:#"This song must be added to your Apple Music library. Please add the song to your library from the Music App."
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:#"Dismiss"
style:UIAlertActionStyleDefault
handler:nil]];
[alertController addAction:[UIAlertAction actionWithTitle:#"Open Music App"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *alertAction)
{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"music://"]
options:nil
completionHandler:nil];
}]];
[self presentViewController:alertController animated:true completion:nil];
return;
}
// good to go? then load it
[musicPlayer setQueueWithItemCollection:mediaItemCollection];
...
}

How to fix view controller hierarchy for AVPlayer?

I have an iOS app with a single player and login. It should work like this:
1) Run app
2) login
3) after login video directly starts playing
4) if you exit the video it should ask for whether you want to logout
5) if no should continue to playing the stream
6) if yes logout the app
7) and if user logs in again without killing app it should repeat the steps from 2-7
However, my code does everything except the last step. If user logs out and logs in
- it starts playing the stream
but when you click the done button and try to exit from video, it does not work. Video stays full screen and it gives the following warning:
Warning: <AVPlayerViewController: 0x7f9fca08aa00> is trying to exit full screen, but is not in its view's window's view controller hierarchy. This results in undefined behavior.
I am really new to xcode ios and objectiveC and my code is as follows
- (void)viewDidLoad {
NSLog(#"ChannelListingViewController::viewDidLoad(): Called...");
[super viewDidLoad];
self.check=1;
NSLog(#"ChannelListingViewController::viewDidLoad(): Exiting...");
}
-(void) playVideo
{
self.channelURL = [TulixAppGlobalConfig getUserSubscribedPlanChannels];
NSURL *url = [[NSURL alloc] initWithString:[self.channelURL[1] getChannelHlsStreamURL]];
self.player = [AVPlayer playerWithURL:url];
// create a player view controller
[self presentViewController:controller animated:YES completion:nil];
self.controller.player = self.player;
[self.player play];
}
-(void) viewDidAppear:(BOOL)animated
{
self.controller = [[AVPlayerViewController alloc] init];
NSLog(#"ChannelListingViewController::viewDidAppear(): Called...");
if(self.check==0)
{
NSLog(#" 0 checkvalue: %i",check);
[self showLogoutConfirmationDialog];
}
else{
self.check =0;
NSLog(#" 1 checkvalue: %i",check);
[self playVideo];
}
[self spawnKeepAliveThread];
NSLog(#"ChannelListingViewController::viewDidAppear(): Exiting...");
}
-(void) showLogoutConfirmationDialog
{
void (^ptrFuncCompletionBlock)(void) = ^(void) { NSLog(#"ChannelListingViewController::showLogoutConfirmationDialog():GENERIC CALL BACK...");
[self attemptToLogoutUser];
};
NSLog(#"ChannelListingViewController::showLogoutConfirmationDialog(): Called...");
UIAlertAction* alertActionYes = [UIAlertAction actionWithTitle:#"Yes" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
//Do Some action here
NSLog(#"ChannelListingViewController::showLogoutConfirmationDialog:: Called for YES...");
self.check = 1;
NSLog(#" yes checkvalue: %i",check);
[self.activeAlterController dismissViewControllerAnimated:NO completion:ptrFuncCompletionBlock];
[self attemptToLogoutUser];
}];
UIAlertAction* alertActionNo = [UIAlertAction actionWithTitle:#"No" style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
NSLog(#"ChannelListingViewController::showLogoutConfirmationDialog:: Called for NO...");
[self playVideo];
}];
NSMutableArray* arrayAlertActions = [NSMutableArray arrayWithObjects:alertActionYes, alertActionNo, nil];
self.activeAlterController = [HelperUtility showMultiButtonDialogHavingTitle:#"Logout" withMessage:#"Are you sure you want to log out?" andAlertActions:arrayAlertActions];
[self presentViewController:self.activeAlterController animated:YES completion:nil];
NSLog(#"ChannelListingViewController::showLogoutConfirmationDialog(): Exit ");
}
I really don't understand why it does not work after the logout and need help.
You just need to relate your avplayer controller to window view. Instead just presenting the avcontroller add below line-
Objective C-
[self.view.window.rootViewController presentViewController:controller animated:YES completion:nil];
Swift 5 -
view.window.rootViewController?.present(controller, animated: true)

Firebase: Stoping it from entering Firebase auth service, when the back button is pressed on specific view

Using firebase to create user sign-in as follow,
a) Sign-up view -> b) Email view -> c) userName view
And have two button namely back and next, all of this can be seen below,
Problem occurs when view b has previously visited view c and upon clicking back button on view b, Firebase falsely goes into user auth function (checked using breakpoints),
Supporting code for back button,
(IBAction)backButtonPressed:(id)sender {
if(page==0)
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Signup" message:#"Are you sure you want to quit signup?" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok = [UIAlertAction actionWithTitle:#"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action)
{
[self.navigationController popViewControllerAnimated:YES];
//BUTTON OK CLICK EVENT
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleDestructive handler:nil];
[alert addAction:cancel];
[alert addAction:ok];
[self presentViewController:alert animated:YES completion:nil];
}
else
{
[self moveToPreviousStep];
}
if (page==3){
self.skipButton.hidden=YES;
}
}
Supporting code for next button with Firebase specific code,
[ARSLineProgress showWithPresentCompetionBlock:^{
[[FIRAuth auth] fetchProvidersForEmail:self.signUpEmail.text completion:^(NSArray<NSString *> * _Nullable providers, NSError * _Nullable error) {
[ARSLineProgress hideWithCompletionBlock:^{
if([providers count]==0)
{
NSLog(#"email is valid for signup");
signingUpFromEmail = TRUE;
[self moveToNextStep];
}
What should happen,
Upon clicking the back button in view b it should go into view a without getting into Firebase auth function, (maybe it's a pending thread for firebase that needs to purged or it can be handled programatically)
Turns out there were two actions assigned to the back button i.e the MoveNextPage and MoveBackPage in storyboard (rookie mistake as the next button was copied and pasted to back button position in storyboard), so the removal of next button action from storyboard corrected the problem.

UIAlertController title and message do not appear in Xcode 8.2

The YES and NO buttons function work as expected, the only problem is that the question No GPS hardware use Triangulation? does not appear to inform the user what the alert is about. The application is tabbed.
The entire code for the project including the xcode project files and Info.plist files can be
found on GitHub, in case you want to build or debug it.
The title and message of the UIAlertController do not appear for the following UIAlertController:
- (UIAlertController*) alertUserNoGPSHardware {
UIAlertController *alertToPresent = nil;
NSString* alertTitleString = #"GPS Alert";
NSString* alertMessage = #"No GPS hardware use Triangulation?";
if (!hardwareExistsOnDevice && mustUseGPSHardware) {
alertToPresent = [UIAlertController alertControllerWithTitle: alertTitleString message:alertMessage
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction actionWithTitle:#"YES" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {mustUseGPSHardware = NO;}];
[alertToPresent addAction:yesButton];
UIAlertAction* noButton = [UIAlertAction actionWithTitle:#"NO" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {mustUseGPSHardware = YES;}];
[alertToPresent addAction:noButton];
}
return alertToPresent;
}
I've also tried to embed the above code in the function that calls this library routine. The same problem occurs.
- (void) setLabelWithGPSLatitudeAndLongitudeWithTimeStampData {
NSString *actionString = nil;
if (displayDataModel) {
if (isFirstGpsClick) {
// Call to the DataModel library that receives a pointer UIAlertView object from the GPS library implementation
// If the UIAlertView pointer is nil proceed with the displaying the latitude, longitude and timestamp.
// If the UIAlertView has a value show the alert, the alert should contain a function to update data in the GPS model.
// This will enable the user to approve of using WiFi or Radio triangulation when the GPS is not available.
/*
* BUG - title and message are not appearing in the alert, the buttons in the alert work as expected
* clicking the YES button removes the warning message that there is no GPS hardware and just
* returns the data. Clicking the no message displays displays the warning message every time.
*/
isFirstGpsClick = NO;
UIAlertController* gpsAlert = [displayDataModel provideGPSAlerters];
if (gpsAlert) {
[self presentViewController:gpsAlert animated:NO completion:nil];
return;
}
}
actionString = [displayDataModel provideGPSLocationData];
}
else {
actionString = #"GPS Button Action Failure: Data Model not created";
}
[displayButtonAction setText:actionString];
}
I've also tried moving the embedded code into the following 2 functions
- (void)viewWillLayoutSubviews {
/*
* Get the tab bar height from the Application Delegate so that the total vertical space
* can be calculated.
*/
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
if (appDelegate) {
UITabBarController *TempTabBar = appDelegate.tabBarController;
if (TempTabBar) {
// Tab Bar Height is larger than myDelegate.tabBarController.tabBar.frame.size.height indicates
tabBarHeight = TempTabBar.tabBar.frame.size.height * 2.5;
}
}
[self setSubViewSizeVariablesBasedOnViewBounds];
[self addButtonAndLabels];
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)viewDidLoad {
[super viewDidLoad];
if (self.displayModelLibraryInitialization) {
NSLog(#"In Objective-C Implementation viewDidLoad - unable to initialize displayModelLibrary");
}
}
When I move the UIAlertController into viewWillLayoutSubviews() or viewDidLoad() I get the black screen, not
the alert and not the buttons and labels that should be there.
This question does not apply because the current problem is in Objective-c and not Swift.
This question does not apply because no textfield is getting updated.
The code does not use alert builder so this question doesn't apply.
Background
I am new to programming in Xcode, iOS, Objective-c and Swift. This is my first iOS project. It was
an interview coding challenge.
OSX - El Capitan
Xcode - Version 8.2 (8C38)
Running in the simulator.
I am only answering this so that it doesn't add to StackOverflow unanswered questions (on CodeReview we would call an unaswered question a zombie).
#DavidH helped provide the answer.
It seems that there are a few minor quirks to the iPhone 7 plus simulator in Xcode 8.2. DavidH was able to see the message in the alert in the iPhone 7 simulator in Xcode 8.3. I switched to the iPhone 7 simulator from the iPhone 7 plus simulator and saw the message in the alert.
This indicates there may not have been a problem in the code and that the iPhone 7 plus simulator in Xcode 8.2 may be buggy.

How can I programmatically dismiss a UIAlertController

I need to programmatically dismiss a UIAlertController that I'm using as a "please wait" message. I can present the alert without problem but when it comes to dismissing the alert, 50% of the time it dismisses and the other 50% it doesn't, forcing me to restart the app just to continue using it. Any ideas how to dismiss the alert with 100% consistency?
//loadingAlert is a UIAlertController declared in the .h file
//present the Alert
loadingAlert = [UIAlertController alertControllerWithTitle:#"Loading..." message:#"Please wait while we fetch locations" preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:loadingAlert animated:YES completion:nil];
//parse JSON file
_listOfAcquisitions = nil;
MNSHOW_NETWORK_ACTIVITY(YES);
NSString *WebServiceURL = [NSString stringWithFormat:#"JSON URL", _search];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSDictionary *dictionary = [JSONHelper loadJSONDataFromURL:WebServiceURL];
dispatch_async(dispatch_get_main_queue(), ^{
_listOfAcquisitions = [NSMutableArray array];
for (NSDictionary *oneEntry in dictionary) {
Acquisitions *acqu = [[Acquisitions alloc] init];
if([oneEntry objectForKey:#"ADDRESS1"] == (NSString *)[NSNull null]){acqu.ADDRESS1 = #"";}
else {acqu.ADDRESS1 = [oneEntry objectForKey:#"ADDRESS1"];}
if([oneEntry objectForKey:#"STATEABBR"] == (NSString *)[NSNull null]){acqu.STATEABBR = #"";}
else {acqu.STATEABBR = [oneEntry objectForKey:#"STATEABBR"];}
if([oneEntry objectForKey:#"TOWN"] == (NSString *)[NSNull null]){acqu.TOWN = #"";}
else {acqu.TOWN = [oneEntry objectForKey:#"TOWN"];}
if([oneEntry objectForKey:#"ZIPCODE"] == (NSString *)[NSNull null]){acqu.ZIPCODE = #"";}
else {acqu.ZIPCODE = [oneEntry objectForKey:#"ZIPCODE"];}
[_listOfAcquisitions addObject:acqu];
}
dispatch_async(dispatch_get_main_queue(), ^{
MNSHOW_NETWORK_ACTIVITY(NO);
[self refreshAnnotations:self];
});
});
});
//finally dismiss the alert...
[loadingAlert dismissViewControllerAnimated:YES completion:nil];
}
I've just been learning how to do this.
So, wherever the alert controller is built, you need to add the action button either to "OK" in default style or "Cancel" in cancel style.
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:#"You made a mistake."
message:#"Pray we don't alter the alert further"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okayAction = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[alertController dismissViewControllerAnimated:YES completion:nil];
}];
[alertController addAction:okayAction];
[self presentViewController:alertController animated:YES completion:nil];
There are other UIAlertActionStyle enumerations, such as UIAlertActionStyleCancel which will put a separator space between other actions and UIAlertActionStyleDestructive which will make the font red but will be in line with other UIAlertActions.
Make sure you add in order: standard actions (Okay, Open Camera, Photo Library) and THEN cancel actions.
There's also preferredStyle:UIAlertControllerStyleActionSheet which is used to set up options for the user. I use this to show Camera and Photo Library.
Also, for your specific dismiss action. The reason it's not working is because you are attempting to dismiss it on the background thread. You should ALWAYS dismiss or make UI changes on the foreground thread. This will cause an NSException/crash in the future.
dispatch_async(dispatch_get_main_queue(), ^{
// dismiss your UIAlertController
});
This is how you should be doing it with your specific code:
dispatch_async(dispatch_get_main_queue(), ^{
MNSHOW_NETWORK_ACTIVITY(NO);
[self refreshAnnotations:self];
[loadingAlert dismissViewControllerAnimated:YES completion:nil];
});
});
You have other issues with your code that you should ask others for help about. I am only a junior developer so I'm not sure how to correctly do what you're trying to do but this should help with dismissing.
You might want to look into loading wheels or toast messages that will say "Please wait" or "Loading".
Using a UIAlertController to show a loading message is rather bad taste.
First of all your network call should probably happen in the completion block of the presentViewController. So you don't dismiss it before it has appeared.
Also the nested dispatch_async seems off, since you call dispatch_get_main_queue(line 32) while already within the mainQue(line 13). And if this were to work the dismiss would need to be within the dispatch_async block so that it actually would dismiss.
But more importantly this is kind of a misuse of the UIAlertController API. Those are intended for user input not to Block UI.
You are better of implementing your own custom view subclass.
Or using MBProgressHUD (https://github.com/jdg/MBProgressHUD). There you can use either the MBProgressHUDModeIndeterminate or MBProgressHUDModeText to accomplish what you are trying.
You're creating/starting/dismissing the alert all within the same block, which means both presentViewController:loadingAlert and [loadingAlert dismissViewControllerAnimated: are being called in the same runloop. This is why you're getting unexpected results.
The dismiss needs to be called from a different cycle of the runloop, so having it called in a separate *block is what you want. You're already doing things in different threads using dispatch_async, which execute in discretely separate runloop so the solution for you is to put the dismissViewControllerAnimated: call within the dispatch_async(dispatch_get_main_queue() to ensure that its both called on the main thread for UI updates, and called in a separate run-loop as its presentation.
You could use dismissWithClickedButtonIndex:animated: but it's now deprecated in iOS9. I would use a timer:
// 3 second delay
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(dismissAlert) userInfo:nil repeats:NO];
- (void) dismissAlert {
[loadingAlert dismissWithClickedButtonIndex:0 animated:YES];
}
Of course, this isn't what UIAlertViews are intended for. You'd be better of using a loading spinner somewhere (UIActivityIndicatorView).

Resources