I have a main view controller which overrides
touchesBegan and plays a sound when hitting a certain screen position. I have a show segue from this view controller to another one, my custom settings view controller. However, when I slide my finger across this new one, the background view controller is still responding to my touches, as a sound is still being played.
I do not have a navigation controller implemented, and it seems like other segue types have this same issue with the touch events responding in the background. Shouldn't the first view controller not be responding to these touches after the segue? Or if I am misunderstanding, what is the proper way to stop touch events being called in the background VC?
Edit: in first view controller, when printing view.isUserInteractionEnabled, I get true despite the view controller being in the background. Not sure if this is relevant.
Edit 2: I tried adding a line in prepare for segue override self.view.isUserInteractionEnabled=false, but weirdly enough, only does this disable touch when dismissing the settings view controller.
Even more strange: if I override touchesBegan in my new view controller, it silences both the touchesMoved and touchesBegan methods in my original view controller. But if I just override touchesMoved in my new controller, the touchesMoved method in both view controllers are called.
I read that we need to override all 4 of the touch handlers and call the superclass method, which I forgot to do. But overriding all 4 methods in both these classes still cause this underlying touch event to go through.
I also would like to stick to this approach rather than a gesture recognizer. I'm assuming there has to be a proper way to handle what looks like this responder chain?
Here's a simple way to replicate: If you create a simple project with two view controllers, A and B, and have a button cause segue from A to B, and implement the touches method in VC A (and let's say print something to the console), then even after segue, the exact same thing happens. VC A is still handling touch events.
If you want to prevent touches on the previous view try presenting the next view to fullscreen.
let controller = segue.destination as! YourViewController
controller.modalPresentationStyle = .fullScreen
Related
It seems that when the UISplitViewController property presentsWithGesture is YES, the UISplitViewControllerDelegate methods aren't called when the master viewController is shown/hidden using swipe gestures. In particular, splitViewController:willShowViewController:invalidatingBarButtonItem: and splitViewController:willHideViewController:withBarButtonItem:forPopoverController: aren't being called.
How are the rest of you managing updating state for your view controllers when using your splitViewController with gestures?
The willHide/willShow methods are for when the master view controller is hidden/shown as the result of an orientation change, and as their parameters suggest, are primarily so that you can add/remove a bar button item for showing the master view controller in a popover. (A split view controller's master popover has a different appearance from other popovers, but it's still a UIPopoverController.)
I still see splitViewController:popoverController:willPresentViewController: being called when I swipe the master view in. And that popover controller's delegate gets notified when the popover gets dismissed.
I have a view controller with animation that run when the view controller appears.
Every time the view controller appears the animation should reset to certain position and replay the animation again.
The viewWillAppear resets the position of the animated subviews.
The viewDidAppear runs CALayer animation.
It works good first time, but when I present a modal view controller and dismiss it, the animated view controller's subviews appear for a small time in the final position of the animation, then it resets to the original position where it starts the animation.
I made sure viewWillAppear is called where it should reset the animated subviews' positions.
Any suggestions are welcome.
Thanks for advance.
I think you intuition is correct: when you dismiss the modal view controller, the underlying view is displayed again and it controller receives the viewWillAppear/viewDidAppear messages.
One possibility you have to fix this is resetting the animation in viewDidLoad, which is called once the view has been loaded in memory (just once if the view is not unloaded). Indeed this method is used to complete the view initialization and seems the right place where to put the reset of the animation.
If this solution is not right for your app (I have no idea about what you are doing overall) and you need to have the reset in the viewWillAppear, you could think of setting a flag in your controller when the modal view is displayed, so you know, the next time your controller receives the viewWillAppear that it comes after the dismiss of the modal view and you don't do the reset. I am not suggesting you to do this, which is pretty hacky and not very resilient, just saying, if you cannot accept the other solution. Much better redesigning your app so that reset is not required in viewWillAppear.
How would I go about implementing dragging and dropping a UIView from UIPopoverController into the back UIView.
This is the functionality that Pages provide in their insert media popover, where you can drag a shape out from the UIPopoverController and drop it into the main document.
I am actually confused with the pan UIGestureRecognizers and where they will be implemented.
Thanks,
Umer
According to the documentation on UIPopoverController, when the popover is presented, it is presented on a special "window". Because of this, simply adding a subview to the popover view controller's content view controller is not sufficient to be able to drag a view outside of the popover view controller's view.
The easiest solution here is to create your own window, add your drag-able view to the window when dragging occurs. Make the window visible for the duration of the drag/drop, and then release your window when complete.
As mentioned above, gesture recognizers (GR) are best suited for Drag/Drop functionality. Once the GR's state has changed to "Began" the GR will control all touches until the "Ended" or "Cancelled" state is achieved which makes it ideal for dragging views between view controllers as well as windows :)
Example:
#interface MySplitViewController : UISplitViewController {
UIView *dragView;
UIWindow *dragWindow;
}
Implementation:
NOTE we do not need to call "makeKeyAndVisible" on our window. We just need to set its "Hidden" property
From Apple in regards to the makeKeyAndVisible method:
// convenience. most apps call this to show the main window and also make it key. otherwise use view hidden property
-(void)dragBegan{
self.dragWindow = [[UIWindow alloc] initWithFrame:self.view.window.frame];
[self.dragWindow addSubview:self.dragView];
[self.dragWindow setHidden:NO];
}
Here we handle the Gesture Recognizer's "Ended" or "Cancelled" state.
NOTE: It is important to remove the window when the Drag/Drop is complete or you will lose user interactiveness with the views below.
-(void)dragEnded{
[self.dragView removeFromSuperview];
[self.dragWindow setHidden:YES];
[self.dragWindow release];
[self.view addSubview:self.dragView];
}
You have to deal with two view controllers one that's in the background called mainController one that presented using a UIPopoverViewController called popoverController. Your popoverController could add a UIPanGestureRecognizer to the views, that the user can drag. The action target of the gestureRecognizer could be a method on the popoverController.
Once the user starts a dragging operation your action method is called with the gestureRecognizer as an argument, were the state of the gestureRecognizer is UIGestureRecognizerStateBegan. You could than save the current frame of the view somewere to be able to animate it back, when the dropping fails. It might be necessary to move the view to an other superview (the window for example), because I'm not sure if UIPopoverViewController clipsToBounds its view.
As the user draggs, your action method is called over and over with the gestureRecognizer in the state UIGestureRecognizerStateChanged. Use the translationInView: method on UIPanGestureRecognizer to determine how much the user dragged and update the dragged views center/frame/transform accordingly.
Once the user lifts his finger the action method is called for a last time with the gestureRecoginzers state set to UIGestureRecognizerStateEnded. Now it's time to find out if the drag was successful. For example the popoverController could ask the mainController via delegation if there's a drop target under the views current position, if so the mainController can take action, else the popoverController would animate the dragged view back to were it came from, and add it back as a subview to it's view.
I hope this is somehow comprehensible and helpful.
I have a UIViewController, and I've added two subviews to its view. One subview is the view of a UIViewController. The other subview is a UITextField.
I need to dismiss the keyboard for the UITextField when the user touches the other view, but I can't figure out how to detect those events. The UIViewController's tableView catches them and breaks the UIResponder chain, so my UIViewController never hears about them. I don't want to subclass everything in the hierarchy just so I can pass the event along up the chain, so what are my options?
I should mention that I'm doing everything programmatically, no IB.
Thanks guys.
So after digging into it, I don't think there's any other way. The responder chain starts with the UIView that received the touch event, if it's not caught passes to that view's controller, then to its superview and so on.
Obviously subclassing every UIView element in a UITableView is insane overkill for this situation.
What I did was create a transparent "touch shield" view with the same frame dimensions as my table view. When the keyboard expands, I add this view over my table, and when it collapses I remove it. This allows me to intercept those touches before they hit the table.
I'm looking for a way to slide the keyboard into view from the right, like what happens in the Contacts application when you edit a note.
My problem is that when I call [someTextView becomeFirstResponder] in viewWillAppear, the keyboard immediatly pops up with no animation. And when I call it in viewDidAppear, the view first slides in from the right (UINavigationController does the sliding), and then the keyboard slides in from the bottom.
Is it possible to have the keyboard slide in from the right, together with the view?
Solution
In iOS 7, calling becomeFirstResponder on _textView in viewDidLayoutSubviews works.
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[_textView becomeFirstResponder];
}
Note: Doing it in viewWillLayoutSubviews also works.
Explanation
Read the discussion in the docs for becomeFirstResponder.
You may call this method to make a responder object such as a view the first responder. However, you should only call it on that view if it is part of a view hierarchy. If the view’s window property holds a UIWindow object, it has been installed in a view hierarchy; if it returns nil, the view is detached from any hierarchy.
When using a navigation controller to push your custom view controller onscreen, self.view.window is still nil by the time either viewDidLoad or viewWillAppear: is called. So, _textView.window is also nil in the same methods, since _textView is a subview of self.view, i.e., they're both in the same window. No matter how you present your custom view controller, self.view.window (and thus _textView.window) is also nil in initWithNibName:bundle:. self.view.window is set by the time viewDidAppear: is called, but that's too late because by that time, the navigation controller has already completed the animation of pushing the view onscreen.
self.view.window is also set by the time either viewWillLayoutSubviews or viewDidLayoutSubviews is called and these methods are called before the push animation of the navigation controller begins. So, that's why it works when you do it in either of those methods.
Unfortunately, viewWillLayoutSubviews and viewDidLayoutSubviews get called a lot more than just on the initial navigation controller push. But, navigationController:willShowViewController: and willMoveToParentViewController: get called too soon (after viewDidLoad but before self.view.window is set) and navigationController:didShowViewController: and didMoveToParentViewController: get called too late (after the push animation).
The only other way I can think of doing it is to somehow observe the window property of _textView so that you get notified when it changes, but I'm not sure how to do that since window is readonly.
All you need to do is tell the text view in question to become the first responder in the viewDidLoad method of the view controller you're pushing onto the navigation stack:
override func viewDidLoad() {
super.viewDidLoad()
someTextView.becomeFirstResponder()
}
This works in iOS 8. The keyboard slides in from the right along with the view.
In iOS 7 (or any version before) you can make a simple thing in loadView, viewDidLoad or viewWillAppear
[yourTextView performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0.0];
In this case you will get left-to-right appearance of the keyboard aligned with the motion of pushing view controller.
For iOS 7 I've found the following solution to work the best for me:
-Import UIResponder-KeyboardCache to your project.
-Add [UIResponder cacheKeyboard:YES]; to the viewDidLoad of the view before the keyboard view. It might be better to do this immediately when the application loads or during a time convenient when you can afford it (during an HTTP request, for example). In most cases, simply in the view before is sufficient.
-Add the following to the viewDidLoad of the keyboard view.
dispatch_async(dispatch_get_main_queue(), ^{
[_textField becomeFirstResponder];
});
To explain, this will preload the keyboard view, which will remove the delay from the first call of the keyboard view. Calling becomeFirstResponder on the text field in the main queue causes it to slide in with the view instead of animating upward before the view slides in.
You could try sending the becomeFirstResponder message to the new view controller before you push it onto the stack. For example:
-(void)functionWhereYouPushTheNewViewController {
yourNewViewController *newVC = [[yourNewViewController alloc] init];
[newVC.yourTextView becomeFirstResponder];
[self.navigationController pushViewController:newVC animated:YES];
}
I have found that changing animations on things like they keyboard is pretty tough though, and if you read the Human Interface Guidelines Apple makes it pretty clear that they want certain things to act in certain ways, all the time. There are ways to change the behaviors of certain animations but they often involve undocumented API calls and are grounds for rejection from the app store. It would be a violation of HIG to have pushed views slide up from the bottom, for example.
Hope this helps.