UIPageViewController - view was not at the correct position initially - ios

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
}

Related

Scrolling a UIScrollView regardless of where the touch occurs

The end goal is to create the same scrolling effect as the one on the "Me" tab of the twitter iOS app where the segmented control rises to the top as you scroll down and then stays fixed to it unless scrolled back up to the top.
The solution I've come up with is illustrated below. There is a view at the top, a segmented control, and a table view beneath the segmented control. All of these elements are embedded inside a scrollview that takes up the entire screen (minus tab & nav bars).
Here is the key issue: If begin scrolling by swiping up from the top-most view or the segmented control, it scrolls the scrollview that all the elements are embedded in. If I scroll the tableview, it will only scroll itself and leave the top-most view and segmented control unaffected.
How can I scroll the scrollview that the elements are embedded in no matter where the scrolling occurs on screen?
I had a similar layout in one of my projects. I used SJSegmentedViewController.
It Requires a headerViewController ,datasource for middle segment and viewController array for those segments.
This library allows you to scroll from anywhere on the screen moreover the segmented control sticks to the top as user scrolls all the way to top.
Here is how you can implement this :
First import the module into your class
import SJSegmentedScrollView
Then create a headerViewController and two viewControllers(Say Video and Tips) for segment
let headerViewController = HeaderViewController()
let video = VideoController()
let tips = TipsController()
After that set these Controller and also set the title for segmented control as following:
segmentController.headerViewController = header
segmentController.segmentControllers = [video,tips]
video.title = "Video"
tips.title = "Tips"
Then add it to the Container View
addChildViewController(segmentController)
containerView.addSubview(segmentController.view)
segmentController.view.frame = self.containerView.bounds
segmentController.didMove(toParentViewController: self)
Here Container View is a UIContainerView
Last but make sure to call in child controllers (VideoController,TipsController), After calling this function in those controllers you can scroll from anywhere on the screen.
extension HomeListingViewController: SJSegmentedViewControllerViewSource {
func viewForSegmentControllerToObserveContentOffsetChange() -> UIView {
//Scrollview in child controllers
return scrollview
}
}
You can find the full documentation here
Hope this helps!

adding a uipageviewcontroller to a uistackview

I'm trying to add a sliding photo gallery functionality to the top portion of a view.
To give context, a user taps on a button or row or something. Then i load a scrollview with a uistackview in it. organized vertically, i had an image, and then another stack view with some text in it. Now, i want that image to become part of a larger "gallery". My research told me to implement UIPageviewcontroller and add the other images to a childVC.
i used this as a tutorial (the first example): http://www.raywenderlich.com/76436/use-uiscrollview-scroll-zoom-content-swift
the only relevant deviation from the tutorial my app has is that it creates things programmatically.
With my proof of concept for the gallery functionality, i wanted to integrate it with the previously mentioned stack view. my plan was to first add the pageviewcontroller stuff into the overall stack view with the original image view right below it and then simply remove the original image view to leave me the final product.
i was able to add the pageviewcontroller.view to the stackview, but the gallery doesn't show. taking a look at the UI Inspector, i can see that the gallery is kinda loaded, but it's messed up.
it's as if the uiview has a frame of 0 height and so the other stack view items don't respect the images that the pageviewcontroller is trying to show.
I think it could be that stack views can only handle specific views, not stuff as complicated as pageviewcontrollers.
also note: my implementation is all programmatic, no storyboards, and so for no xibs. so maybe i missed something here.
here is some code, if it helps:
note the constrain functions you see are from the "cartography" pod
this adds the "gallery" to the stack view, it's a delegate function from my view
func addZoomStuff(sender: UIStackView) {
let zoomer = PageBaseViewController()
addChildViewController(zoomer)
zoomer.view.translatesAutoresizingMaskIntoConstraints = false
zoomer.view.tag = 5
sender.addArrangedSubview(zoomer.view)
zoomer.didMoveToParentViewController(self)
}
this is what creates the scrollview, image view, etc for the gallery items:
override func viewDidLoad() {
super.viewDidLoad()
//MARK: - Zoom View Elements
// prep
scrollView = UIScrollView(frame: self.view.frame)
scrollView.delegate = self
self.view.addSubview(scrollView)
constrain(scrollView, view) { view, view2 in
view.edges == view2.edges
}
self.view.setNeedsUpdateConstraints()
// 1
let image = UIImage(named: imageName)!
imageView = UIImageView(image: image)
// 2
scrollView.addSubview(imageView)
constrain(imageView){ view in
view.edges == view.superview!.edges
}
scrollView.contentSize = image.size
i tried adding the constraints like this but there was no effect
func addZoomStuff(sender: UIStackView) {
let zoomer = PageBaseViewController()
addChildViewController(zoomer)
view.addSubview(zoomer.view)
constrain(zoomer.view, view) { view, view2 in
view.width == view2.width
view.height == view2.height * 2 / 3
view.leading == view2.leading
view.top == view2.top
}
zoomer.view.setNeedsUpdateConstraints()
zoomer.view.removeFromSuperview()
zoomer.view.translatesAutoresizingMaskIntoConstraints = false
zoomer.view.tag = 5
print("sender.subviews: \(sender.subviews)")
sender.addArrangedSubview(zoomer.view)
zoomer.didMoveToParentViewController(self)
print("sender.subviews: \(sender.subviews)")
}
if this method isn't going to work, can i do a nested horizontal stack view instead of the pageviewcontroller and somehow get that same scrolling/snap effect to see on image view at a time?
TLDR;
Create a subclass of UIPageViewController, make it it's own delegate.
Initialize the subclass with a plain UIViewController, only set a backgroundcolor.
In the pageviewcontroller subclass, implement the two delegate callbacks for a next and previous viewcontroller: create a plain viewcontrolller, with some random backgroundcolor.
If this works, replace the plain viewcontroller by your actual contentviewcontroller.
Long version:
Have you seen this: Maybe this link will help: https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/PageViewControllers.html
It might help, as it explains the details of UIPageViewController. Basically, you need to create a viewController (not a view!), that shows one page of the gallery. So this VC has a stackview, and manages the content of it. The pageviewcontroller is initialized with your first contentviewController. If you create a subclass of the uipageviewcontroller, you can set self of that subclass as the delegate of it. Implement the delegate callbacks that return the next or previous viewController and thats it. For this last part, it is convenient to have a property on the contentviewcontroller from which the subclasses of the pageviewcontroller can figure out what data to set on the next or previousviewcontroller.
Your title seeks to hint at some confusion: its not possible to add a viewcontroller to a view. You can only add other views to a (stack)view. A viewcontroller owns and manages a viewhierarchy. A pageviewcontroller has no content, but manages the insertion and removal of viewcontrollers. as the pageviewcontroller is a containerviewcontroller, it will als take the contentViewcontrollers' views and place them in the viewhierarchy. But this is not something your code has to do when you subclass UIPageViewControlller and implement it's delegates on itself (and don't forget to assign self to be the delegate).

Two UIViewControllers inside UIScrollView - resigning first responder

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.

Swift: The best way to show and hide different views based off of my UISegmentedControl?

I want to show one view when the segmented control highlights the first option. When the user highlights the other option, I want the first view to disappear (or hide) and the other view to become visible. Then, if the user presses the first option again, the second view hides and the first becomes visible.
What is the best way to do this?
I do not want to switch ViewControllers, but simply Views who are both using the same ViewController.
A way to do that would be to give a tag to each view (as in your segment's indices) and call a method when the its value is changed that would hide all view's accept the one with the right tag number.
You could hook the UISegmentedControl from the view controller to the code using an #IBAction and use the following method:
#IBAction func switchView(sender: UISegmentedControl) {
// Change your view controller's view property
// to reference whatever custom views you have.
if(sender.selectedSegmentIndex == 0) {
self.view = viewOne
} else {
self.view = viewTwo
}
}
viewOne and viewTwo being UIViews

Incorrect Popover size - when displaying UITableViews under Navigation Controller in Popover

I am writing an iPad app which features UITableViews displayed under a Navigation Controller within a UIPopoverController.
The popover is displayed when I pick a button in the Main View Controller of my app. The popover opens displaying a first TableViewController, which has two rows (UITableViewCells) - "Search" in the first row and "Advanced Settings" in the second row. On initial display, the popover is sized just enough to display the two rows.
I have coded this first TableViewController's didSelectRowAtIndexPath such that when I pick "Search", it pushes a second TableViewController onto the NavigationViewController. This next View Controller allows the user to perform a search, and search results then get populated in its tableview.) This (search results) table view controller is sized long enough to accommodate all the rows returned by the search. The search popover therefore becomes longer when displaying the search tableview controller.
When I cancel the search (or hit the back button in the navigation bar) the popover returns to displaying the first table view controller (the one with just two rows). However this first table view controller now has the longer size. In other words, the popover, instead of resizing itself back to a two row table, remains the size of the second (search results) table view controller (so it now has the two rows "Search" and "Advanced Settings" plus a number of empty rows)
My question is: how can I get each tableview controller in the hierarchy in this implementation (i.e. where table view controllers are displayed in a popover under a navigation controller) to be sized individually and to return to its original size when the user navigates back and forward. There is probably a simple solution to this, but it escapes me! Appreciate if someone can point me to a solution.
As the above solution does not work anymore, here's a more current (Swift) alternative.
You can pass along the popovercontroller to your destinationViewControllers.
Then call preferredContentSizeDidChangeForChildContentContainer in viewWillAppear() and the popover will resize automatically.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if let ppc = popoverController {
ppc.preferredContentSizeDidChangeForChildContentContainer(self)
}
}
If it doesn't work make sure you properly implement the preferredContentSize. For example calculating the size of the your tableViewController with a single section as such:
override var preferredContentSize: CGSize {
get {
let sectionFrame = self.tableView.rectForSection(0)
let titleOnTop = self.navigationController!.navigationBar.frame.height
let height = sectionFrame.height + titleOnTop
return CGSize(width: super.preferredContentSize.width, height: height)
}
set { super.preferredContentSize = newValue }
}
I implemented the answer from the following StackOverflow post by user #krasnyk :
Popover with embedded navigation controller doesn't respect size on back nav
It worked great for me with one change ...
Basically added the same function detailed in the above post with one modification (I hardcoded the size for each VC in my view heirarchy in the PopupController)
I referenced this function to set the correct popover size in the ViewDidLoad and ViewDidAppear functions for each VC in the chain of VCs displayed in my PopoverController.
- (void) correctPopoverContentSize {
//
// removed the following line from the original code in above post as it did not
// work for me
// CGSize currentSetSizeForPopover = self.contentSizeForViewInPopover;
CGSize currentSetSizeForPopover = CGSizeMake(320.0f, 180.0f);
CGSize fakeMomentarySize = CGSizeMake(currentSetSizeForPopover.width - 1.0f,
currentSetSizeForPopover.height - 1.0f);
self.contentSizeForViewInPopover = fakeMomentarySize;
self.contentSizeForViewInPopover = currentSetSizeForPopover;
}

Resources