Is there a way to prevent the user from dismissing a modal controller?
I think it is pretty common to want to "block" the main Watch App interface while asking the user to open the iPhone counterpart or to perform some action there.
My current solution is to present again the controller when it gets dismissed but its clunky.
There's a somewhat kludgy way to get around this issue using reloadRootControllers. When you call reloadRootControllers(withNamesAndContexts:) with the name of a WKInterfaceController that you've named in your storyboard, it has a similar effect to presenting that controller modally. However, since it's now the root controller, it doesn't have a cancel button. I don't really like this, but it does get the job done.
Note this method is deprecated since watchOS 4.
Apple Documentation on reloadRootControllers(withNamesAndContexts:)
The trick is to make the modal screen fullscreen and change the inset top value for your main group.
You can't prevent a modal interface controller from being dismissed, as the system automatically dismisses it when the title is tapped.
Since your code isn't asked if it should happen, but only knows that it is happening, there's no way to intercept or cancel that action. The WKInterfaceController documentation briefly touches on this.
When the user taps the title string, WatchKit automatically dismisses the modal interface without taking any further actions.
What can you do?
While you don't know when the Cancel title is tapped, there is a hack which "hides" the Cancel title.
This may confuse users who might wonder how to dismiss the modal, or mislead others into thinking the modal couldn't be dismissed.
What does the HIG recommend?
Circumventing a Human Interface Guideline would likely degrade the entire user experience.
The top-left corner of the modal sheet is reserved for the Close button, which dismisses the interface.
Some users might be frustrated or annoyed if
there is no apparent way to cancel, or
the modal presents itself again after repeatedly being cancelled.
Since the user expects to be able to dismiss the modal, perhaps you could allow them to do just that, then simply display some form of reminder in the presenting interface controller (to log in, or enable permissions).
Related
iOS 7 introduces edge swiping to navigate backwards in a navigation controller (so long as you don't override the leftBarButtonItem, among other things).
Imagine I'm implementing an app similar to Apple Notes. In Apple's app, once the user taps into a note, there is a Back button on the top left, and a Done button on the top right. This view controller is automatically saved after the user navigates away. Even if the user swipes backwards while still editing the note, it will auto-save.
One way to implement this is to write to disk on each key stroke. However, I'm looking for a more efficient implementation.
Which method should I override to perform my auto-save?
To be clear, the auto-save code should be invoked in these three cases:
The user taps the Back button
The user swipes backwards
The user taps the Done button
So far, I've been implementing the save logic in viewWillDisappear, this minimizes the amounts of writes, and also takes care to save if the user kills the app.
Additionally, instead of implementing save logic in the Done button, the Done button can simply call:
[self.navigationController popViewControllerAnimated:YES];
... (e.g. via a delegate, or unwind segue) and it will automatically save as well. All three code paths will go through viewWillDisappear.
Although it could technically be implemented in viewDidDisappear, if the previous view controller needs to display the updated data, this method will be too late.
For more complex view controllers (e.g. those that have destructive behavior once the view controller is dismissed), there are several other things to consider:
Should the save be called if a modal is presented above the current view controller? (e.g. In Apple Notes, imagine that the share button launched a modal; should it save when you tap on this button?). If it is important not to save at this time, you can read the value of presentedViewController. If it has a value, that means viewWillDisappear is being called because a modal is being presented above it.
Should the save be called if a view controller is pushed on to the current navigation stack? (e.g. Again, in Apple Notes, if the share button used a push instead of a modal, should it save?) You can check the value of isMovingFromParentViewController in this case.
If the user taps the home button to quit your app, should it save?
If the user begins swiping backwards then cancels the swipe to stay on the current screen, should it save?
Imagine the view controller is in a modal, when the modal is dismissed should the destructive action happen? Check isBeingDismissed in this case.
Same as above, but imagine the view controller is the second page in a navigation controller than is being presented.
Is there any way to know when the cancel/dismiss button is tapped on the Apple Watch?
- (void)didDeactivate
Is not an option because that is called whenever the watch goes dark. I'm trying to sync data with the parent iPhone app and this makes it tough to cancel an operation on the parent app.
While you can't get the cancel directly, you can tell which view controller is active (because your code displayed it) and then tell when they switch. So you can set some flags on display and on de-activate, to tell when the view controller with the cancel button goes away.
Think of it another way, if your view controller disappears and another one displays, the activate of the other controller tells you the watch did not sleep.
Unfortunately, the current version of WatchKit has no method to determine if the Cancel button is tapped. The closest you'll get is the didDeactivate event that you've already mentioned.
I have a presentedViewController on top of the root view controller when the app gets dismissed. (e.g. User navigates to another app or goes back to the home screen.)
I would like to nil it out when the user reactivates the app without it being visible to them. Calling -dismissViewControllerAnimated: is not an option because it only works if the view controller is visible, and I'd like to do it sooner and specifically only in application:openURL:sourceApplication:annotation: and otherwise let the user continue their workflow in the modal view.
Are there any tricks I can use?
According to your comment, the only actual issue is what happens when the app receives application:openURL:sourceApplication:annotation: and is thus brought to the front.
In that case, simply adjust your interface in application:openURL:sourceApplication:annotation: - that is part of its purpose, to allow you to do that. You will be given some time before the "snapshot" is torn away, so if the adjustment involved is to dismiss an existing modal view, dismiss it without animation and there you are.
The user will still see the modal view for a moment, but not really - what the user is seeing is not your interface but the "snapshot" that was created by the system when the app when into the background. The "snapshot" is removed to reveal the actual interface (with a nice crossfade effect in iOS 7).
If the "snapshot" itself is problematic there are ways of preventing it from being taken, but it doesn't sound to me like this is that kind of situation.
(By the way, this raises the question of how the presented view is to know that it must be dismissed. The initial main action is in your app delegate, which is probably some conceptual distance away from the presented view. This would be an appropriate situation in which to use an NSNotification to communicate across this distance.)
I'm trying to add some finer control to the Storyboard controller and I hit a blockage with the back button.
I have a basic Storyboard with push transition. I want to be able to catch when the user presses on the back button (the one generated automatically) and decid if I want the view to go back or not.
The scenario is to show a message to the user asking if he wants to go back warning him that he is going to lose his work if he does,
Sounds simple, yet I can't find how to do it.
Any ideas
You don't get any sort of message by default when the back button is pressed. If you want to provide this sort of functionality, you have two options:
1) Provide a custom back button and set it as the leftNavigationItem of your UIViewController's navigation bar
2) Subclass UINavigationController and override a method such as popToRootViewController:animated:
Try using UIAlertView Delegate.
In your buttonPressed Action, provide an alert view with message with two buttons YES and NO.
define button at index action for YES and NO -ie- YES: dismiss current view and NO: remain on current view.
Search Apple Docs on alert view delegate
I know it's a very long shot and the problem description is vague at best, but I'm hoping someone might have encountered that as well.
I have a UIViewController subclass that is being presented modally. The view of the controller contains some UIButtons and some UITextFields. The view controller might be presented multiple times during a user session; it is created every time it's presented and gets destroyed when it's dismissed. Most of the time everything works as expected, however, sometimes the text fields don't respond to user touch, i.e. tapping them does not bring up the keyboard. The buttons on the view work fine, it's just the text fields that become disabled.
What could possibly be causing this?
Turns out the reason UITextFields inside a modally presented view controller were not responding was that one of the long-living view controllers in the app called [self becomeFirstResponder] on itself, but never called [self resignFirstResponder]. It had to be the first responder in order for it to receive remote controls events when the app was playing music.