Two UIViewControllers inside UIScrollView - resigning first responder - ios

In the app I am working on I have two view controllers inside a scroll view. The second view controller (VC2) contains a text view. You can see the setup on the image below:
!
When I scroll from VC2 to VC1, the keyboard persists and covers the content of VC1. I managed to solve the problem by making the scroll view the first responder on scrollViewDidScroll event. This works, but it results in the keyboard disappearing even on a partial scroll, which can be annoying to the users. I can solve this problem by also checking the content offset but it strikes me as overcomplicated and not elegant at all. Is there a better way to do this?
Edit:
As Chonch and latenitecoder suggested, I detected the page change. I adapted the code from: Detecting UIScrollView page change to swift. Here it is:
var previousPage = 0
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let pageWidth = scrollView.frame.size.width
let fractionalPage = scrollView.contentOffset.x / pageWidth
let page = Int(round(fractionalPage))
if (previousPage != page) {
// Page has changed, do your thing!
self.becomeFirstResponder()
// Finally, update previous page
previousPage = page
}
}

You can set the UIScrollView's pagingEnabled property to YES and only call resignFirstResponder for the UITextView when the paging ended and the resulting page is VC1. As long as the current page remains VC2, you don't call resignFirstResponder, and the keyboard remains shown.
However, notice that it may actually be a good idea to hide the keyboard as soon as the user starts scrolling (as you're describing is your current state). Maybe you should leave it like this and when the paging ends, check if the current page is VC2, and if it is, call becomeFirstResponder on the UITextView in order to display the keyboard again.

I'd suggested to use UIPageViewController to horizontal scrolling controllers. Then you can do the same action in it's delegate method
- (void)pageViewController: didFinishAnimating: previousViewControllers: transitionCompleted: but it will save you memory a lot.

Related

How to fix highlighted state UIButton delay in UIPageViewController page?

I have a screen in which I have a UIPageViewController in each page I have a UIButton. The problem is that there is a delay of the pressed/highlighted state of the button for about a half second when the user presses the button. both state images are set to the button using the storyboard.
This happens in the Simulator as well as on a real device.
Now from my Google searches, I came across a few posts that describe this issue, for example:
UIButton delayed state change
and:
UIbutton only looks clicked (highlighted) on longPress?
In all posts, the solution is to use the delaysContentTouches setting and set it to false.
The problem is: I didn't found how would I apply this in my case of a UIPageViewController. most of the posts talk this issue in a UIScrollView or a UITableView.
So, the question is: how would I do that in case of a UIPageViewController? I didn't see that UIPageViewController has this setting and didn't find any other way to apply it.
Found a solution to this issue, This piece of code will fix the button highlighted click delay but will prevent the pager scroll on the button itself.
public override func viewDidAppear(_ animated: Bool) {
for view in self.view.subviews {
if view is UIScrollView {
(view as? UIScrollView)!.delaysContentTouches = false
}
}
}
The reason I didn't find this in UIPageViewController is that UIPageViewController is not a subclass of UIScrollView as I expected but it contains it as a subview.

scrollViewDidScroll is not getting called

I have a custom tab bar with a horizontal scroll view underneath that.
Tab bar contains 5 buttons each of them calls one of the five custom keyboards (they are placed on a scroll view). You can switch between these keyboards scrolling the scroll view or pressing the buttons.
Question: how do I change the states of the buttons (default <-> selected) when I switch between keyboards using scroll view?
Visual:
I downloaded your project and you have some issues there. You're implementing the UIScrollViewDelegate but for some reason, the method you have in your app is wrong for the scrollViewDidScroll(_:).
The method needs to be declared like in this way:
func scrollViewDidScroll(_ scrollView: UIScrollView) {}
You cannot change anything in that method and in your case you changed the name of the argument as the name of your UIScrollView, just a minor change but it implies that you will not be notified every time the UIScrollView did scroll.
So let's go back to your problem. To achieve what do you want you to need to calculate the current page where you are inside the UIScrollView or in your case the number of the button, we can calculate that using the following formula:
let pageWidth = scrollView.frame.size.width
let page = Int(round((scrollView.contentOffset.x ) / (pageWidth)))
Afterwards what you need is update your selected button when the scroll finish of scrolling. The problem with the scrollViewDidScroll(_:) method is that this method is called several times during the scrolling process, so we need a method that is called only when the scrolling is finished, and it's the scrollViewDidEndDecelerating(_:).
So you need to add the following code in your code:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageWidth = scrollView.frame.size.width
let page = Int(round((scrollView.contentOffset.x ) / (pageWidth)))
let previousIndex = selectedIndex
tabBarButtons[previousIndex].isSelected = false
selectedIndex = page
tabBarButtons[page].isSelected = true
}
I tested the behaviour in your app and works fine. If you have any trouble I have your project modified, but the only thing you need is to add the above method in your UIViewController and it should work fine
I hope this help you
UIScroll has a delegate method called scrollViewDidScroll. This gets called every time the user scrolls. The scrollView has a property called contentOffset. That property tells you exactly how much as been scrolled. Use this method and property to branch your logic.

UIPageViewController - view was not at the correct position initially

I am using UIPageViewController to create the introduction pages of my app.
As the gif(I swiped to the 2nd view and swiped back to the 1st view):
My second page's view was not at the correct position initially. It's upper than it should be and dropped down later. However, if I swiped back, the first page's view was correct.
I guess it's because the UIPageViewController didn't load my 2nd view early enough, so the auto layout system was still calculating the position when the 2nd view already appeared. When I swiped back to the 1st view, since the view was already loaded, there was no such issue.(it's just my guess, I am not sure.)
I found I can use 2ndViewController.loadView() as a workaround, but Apple's document discourages programmers to call this method directly. I also found calling this method directly is buggy.
How do I prevent this correctly?
If you are setting constraints, fix the view's top space from Superview.
In func create view at index, let try my code
func pageTutorialAtIndex(index: Int) ->TestNodeController
{
let pageContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("TestNodeController") as! TestNodeController
pageContentViewController.automaticallyAdjustsScrollViewInsets = false
pageContentViewController.pageIndex = index
return pageContentViewController
}

UIPageViewController prevent hiding keyboard on scroll

I have a UIPageViewController with multiple UIViewController, each one containing a UITextField.
Whenever I slide the UIPageViewController, the keyboard automatically dismisses. Is there a way to prevent this behavior?
Invoking becomeFirstResponder on the new UITextfield but the animation the won't fix the problem.
You can try to embed the PageViewController as a ChildViewController of an other viewController.
I believe this way the navigation in the PageViewController will not effect the keyboard.
I am not sure if that is needed, but if it is still not working you can set your ParentViewController as the firstResponder when transition occurs.
unfortunately this seems to come from the animation of the transition showing as done, before it is actually done,
the workarounds I can think of are
1. made the animating false
2. set textFieldShouldEndEditing in the next VC to return NO or handle it with a bool
3. add a delay in the animation, or in the next VC viewWillAppear
When using setViewControllers on UIPageViewController (setting a controller without the scroll), it seems that the completion block is called just before the scrollView has reached its final position. When it does, it dismisses the first responder.
The solution we found was to first, grab the scroll view:
// In UIPageViewController subclass
for subview in self.view.subviews {
if let scrollV = subview as? UIScrollView {
scrollV.delegate = self
self.scrollView = scrollV // Optional, we don't really need it
}
}
The scroll view position is done when its offset is the x position of the middle controller. That is, the offset will equal the view's length.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x == self.view.width {
// Transition is 'really' done.
}
}
At that point, you can send a notification that transition is completed.
What we did, is hold a completion block and call it when the transition is done.
Create a function, so that a controller can pass a block in:
func transitionCompleted(completed:(()->())?) {
self.transitionCompletedBlock = completed
}
When transition is completed:
self.transitionCompletedBlock?()
The controller with the keyboard will look like:
pagerController.transitionCompleted(completed: {
self.textfield.becomeFirstResponder()
})

dismissModalViewControllerAnimated resets contentOffset

I have a problem with my table view. When dismissing a modal view controller presented on top of it, it always scrolling to the top . I have tried observing the changes to contentOffset using KVO, but the one that messes my view goes behind it.
From the UITableViewController, when user finishes his task in the modal dialog, self.tableView.contentOffset is , I call:
[self dismissModalViewControllerAnimated:YES]
Subsequently, when the viewWillAppear:(BOOL)animated is called, the self.tableView.contentOffset is already set to 0,0.
Is this supposed to be happening? I am able to work around the issue by remembering the scroll position before presenting the modal view and restore it back in viewWillAppear after dismissing the modal view. But it seems wrong. Am I missing something?
I have found similar problem described in Dismiss modal view changes underlying UIScrollView.
It looks like this is default behavior of UITableViewController. I tested it in very simple app and It worked exactly as you said. If you don't like it, use UIViewController instead.
Here is how I work around this problem, so that the table view maintains the original scroll position. In my subclass of UITableViewController I have added:
#property (assign) CGPoint lastScrollPosition;
Then in the implementation, I have overridden the following:
- (void)viewWillAppear:(BOOL)animated
{
self.tableView.contentOffset = self.lastScrollPosition;
}
- (void)dismissModalViewControllerAnimated:(BOOL)animated
{
self.lastScrollPosition = self.tableView.contentOffset;
[super dismissModalViewControllerAnimated:animated];
}
If you want your table to initially appear scrolled to non-zero position, as I did, don't forget to initialize the lastScrollPosition in your viewDidLoad.

Resources