UIPageviewController inside a UIScrollView - ios

I want to code a reader in swift.
I use a pageViewController for this. To enable ZoomIN/ZoomOut in use a UIscrollView, my idea is to put inside this scrollView the pageViewController.
Effectively, it works. But my problem is only in the position on screen, the pageview controller is cropped.
My code:
class RootViewController: UIViewController,
UIPageViewControllerDelegate,
UIPageViewControllerDataSource,
UIScrollViewDelegate
{
#IBOutlet weak var scrollView: UIScrollView!
var isLandscape:Bool = Bool()
var pageData:[String] = []
var pageViewController:UIPageViewController = UIPageViewController()
//MARK: ViewDid/Should methods
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
loadData() // Load the images URL
// Set up the pageViewController
pageViewController = UIPageViewController(transitionStyle: UIPageViewControllerTransitionStyle.PageCurl, navigationOrientation: UIPageViewControllerNavigationOrientation.Horizontal, options: nil)
pageViewController.delegate = self
pageViewController.dataSource = self
var startingViewController:DataViewController = self.viewControllerAtIndex(0) as DataViewController
var viewControllers = [startingViewController] as NSArray
pageViewController.setViewControllers(viewControllers, direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
self.addChildViewController(pageViewController)
self.contentView.addSubview(pageViewController.view)
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
var pageViewRect:CGRect = self.view.bounds
pageViewRect = CGRectInset(pageViewRect, 10.0, 20.0)
self.view.frame = pageViewRect
self.pageViewController.didMoveToParentViewController(self)
scrollView.minimumZoomScale = 1
scrollView.maximumZoomScale = 2.5
scrollView.zoomScale = 1
scrollView.setZoomScale(1.0, animated:false)
}
}
func viewForZoomingInScrollView(scrollView:UIScrollView) -> UIView {
// Return the view that you want to zoom
return pageViewController.view
}
Screenshot:
Can somebody can help me to fix this.
Thanks.

Related

Child view controller's view ignores Auto Layout constraints

I would like to add child view controller to UINavigationController's view. I have hierarchy like this
NavigationController -> View(drawer) -> View(contentContainer) -> child view controller should be pinned to the contentContainer
But for some reason it ignores the constraints and I get weird results. Please see the screenshot, drawer is green, contentContainer is yellow and the child controller is placed almost outside of the screen and has frame with 0 height.
The critical code is inside the drawerContentController didSet and setupView method. Please note that I'm using SnapKit for constraints but the same problem was happening by setting the NSLayoutConstraints traditionally
class NavigationController: UINavigationController {
private var drawerHeightConstraint: Constraint!
fileprivate lazy var drawer = UIView()
private lazy var contentContainer = UIView()
var drawerContentController: UIViewController? {
didSet {
guard let new = drawerContentController else {
oldValue?.removeFromParent(animated: true)
return
}
if let old = oldValue {
old.removeFromParent(animated: true) { _ in
self.add(child: new, superview: self.contentContainer, animated: true, completion: nil)
}
} else {
add(child: new, superview: contentContainer, animated: true, completion: nil)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
private func setupView() {
view.addSubview(drawer)
drawer.backgroundColor = .green
drawer.snp.makeConstraints { make in
make.leading.bottom.trailing.equalToSuperview()
drawerHeightConstraint = make.height.equalTo(360).constraint
}
let blur = UIBlurEffect(style: .default)
let visualView = UIVisualEffectView(effect: blur)
drawer.addSubview(visualView)
visualView.pinToSuperView()
contentContainer.backgroundColor = .yellow
drawer.addSubview(contentContainer)
contentContainer.snp.makeConstraints { make in
make.edges.equalTo(drawer.safeAreaLayoutGuide)
}
}
func add(child controller: UIViewController, superview: UIView? = nil, animated: Bool, completion: ((Bool) -> Void)? = nil) {
controller.willMove(toParent: self)
addChild(controller)
controller.view.alpha = 0
(superview ?? view).addSubview(controller.view)
controller.view.pinToSuperView()
UIView.animate(withDuration: animated ? 0.3 : 0, animations: {
controller.view.alpha = 1
}) { finished in
controller.didMove(toParent: self)
completion?(finished)
}
}
}
extension UIView {
func pinToSuperView() {
snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
When I add a simple UIView to the contentContainer it works as expected
var drawerContentController: UIViewController? {
didSet {
let v = UIView()
v.backgroundColor = .purple
contentContainer.addSubview(v)
v.pinToSuperView()
}
}
Okay, I "solved" the issue.
It seems that UINavigationController does not like having child view controllers added to it. As seen on the screenshots, when child controller is added the entire view hierarchy of navigation controller is broken. On the second screenshot, the table view controller (white view) is displayed correctly, but in the first case it is not even in the view hierarchy!
So the moral is, try to not add child view controllers directly to UINavigationController, in the end it is not a normal UIViewController and doesn't even have a normal UIView. I have added another UIViewController to act as a container for UINavigationController and added the child controller to it. It now works as expected.

UIScrollView paging layout on iPad and iPhone

I am making an universal demo xcode project which is a UIScrollView contains two pages, scroll left and right to go back and forth.
My questions is iPad and iPhone's layout is not the same.
I created 3 view controllers in storyboard as below:
ViewController has the UIScrollView.
AViewController contains an UILabel in the center("Good morning...").
BViewController contains an UILabel in the center("Good night...").
The constraints of the UILabels are:
Here is the code of ViewController:
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let story = UIStoryboard(name: "Main", bundle: nil)
let vc1 = story.instantiateViewController(withIdentifier: "AViewController")
let vc2 = story.instantiateViewController(withIdentifier: "BViewController")
vc1.view.backgroundColor = UIColor.green
vc2.view.backgroundColor = UIColor.red
addContentView([vc1, vc2])
scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width * 2, height: scrollView.frame.height)
}
func addContentView(_ viewControllers: [UIViewController]) {
viewControllers.enumerated().forEach {
addChildViewController($1)
$1.view.frame = CGRect(x: UIScreen.main.bounds.width * CGFloat($0), y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
scrollView.addSubview($1.view)
didMove(toParentViewController: $1)
}
}
}
If I choose View as iPhone 8 then the layout works correct in iPhone 8 like this: (Swipe then from green to red)
But for iPad:
The labels are not centered!!.
If I choose View as iPad ... ,then for iPhone it doesn't layout correct.
Here is the view hierarchy, it's obvious the layout is correct but for some reason the green view is larger than screen size and covered by the red one.
Thanks for any help.
The code is here: https://github.com/williamhqs/TestLayout
The embedded view controller views have translatesAutoresizingMaskIntoConstraints = true by default which introduces bogus constraints:
Setting it to false will not let you just set the frame manually, so you will have to write the full layout. It's slightly more verbose, but autolayout will work for the embedded view controllers and you get rotation support as well:
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let story = UIStoryboard(name: "Main", bundle: nil)
let vc1 = story.instantiateViewController(withIdentifier: "AViewController")
let vc2 = story.instantiateViewController(withIdentifier: "BViewController")
vc1.view.backgroundColor = UIColor.green
vc2.view.backgroundColor = UIColor.red
addContentView([vc1, vc2])
}
func addContentView(_ viewControllers: [UIViewController]) {
var previousController: UIViewController? = nil
viewControllers.forEach {
addChildViewController($0)
$0.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview($0.view)
$0.view.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
$0.view.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
if let trailing = previousController?.view.trailingAnchor {
$0.view.leadingAnchor.constraint(equalTo: trailing).isActive = true
} else {
$0.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
}
$0.view.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
$0.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
didMove(toParentViewController: self)
previousController = $0
}
previousController?.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
}
Scroll view works as expected for all devices, regardless of the Xcode View as setting:

How do make a UIScrollview only show one subview at a time

I followed a tutorial: https://www.youtube.com/watch?v=1_daE3IL_1s that teaches you how to make a snap-chat like menu in swift 3, where every time you swipe a new subview is put on the screen, but it is the only one and you need to swipe another time to get the next view. This tutorial was made in swift 2, but when I updated to swift 3 the scrollview is now acting like normal scroll view where it scrolls fluently. is there any way to fix this?
code for main view controller:
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let V1: View1 = View1(nibName: "View1", bundle: nil)
let V2: View2 = View2(nibName: "View2", bundle: nil)
let V3: View3 = View3(nibName: "View3", bundle: nil)
V1.didMove(toParentViewController: self)
self.addChildViewController(V1)
self.scrollView.addSubview(V1.view)
V2.didMove(toParentViewController: self)
self.addChildViewController(V2)
self.scrollView.addSubview(V2.view)
V3.didMove(toParentViewController: self)
self.addChildViewController(V3)
self.scrollView.addSubview(V3.view)
var V2Frame: CGRect = V2.view.frame
V2Frame.origin.x = self.view.frame.width
V2.view.frame = V2Frame
var V3Frame: CGRect = V3.view.frame
V3Frame.origin.x = 2 * self.view.frame.width
V3.view.frame = V3Frame
self.scrollView.contentSize = CGSize(width: self.view.frame.width*3 , height: self.view.frame.size.height)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Enable paging:
override func viewDidLoad() {
super.viewDidLoad()
scrollView.isPagingEnabled = true
// ...
}

ScrollView paging

I want to create the slide to unlock animation like it is there on the iPhone lock screen. I want the user to swipe right so that another view comes to the front. How should I make this? I have tried this, and here is my code:-
import UIKit
class PageViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setUpScrollView()
}
func setUpScrollView () {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let aViewController = storyboard.instantiateViewControllerWithIdentifier("Hello") as! HelloViewController;
let bViewController = storyboard.instantiateViewControllerWithIdentifier("Home") as! HomeViewController;
let viewControllers = [bViewController, aViewController]
scrollView.pagingEnabled = true
scrollView.contentSize.height = 600
var contentSizeWidth = CGFloat(2) * self.view.frame.width
scrollView.contentSize.width = contentSizeWidth
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
scrollView.scrollsToTop = false
scrollView.delegate = self
// add all views to scrollView
for (index, vc) in enumerate(viewControllers) {
var frame = self.view.frame
frame.origin.x = frame.width * CGFloat(index)
frame.origin.y = 0
vc.view.frame = frame
self.addChildViewController(vc)
self.scrollView.addSubview(vc.view)
vc.didMoveToParentViewController(self)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The problem that I face is, that the view on the left is cropped somehow. To make you visualise this, the view with the slide to unlock label appears when the iOS simulator boots up. But when I swipe right, the scrollView works perfectly, just the second view is cropped (it is on the extreme left, occupying less than half of the screen that it should ideally fully occupy. Please help me out here. Thanks in advance.

Disable UIPageViewController bouncing - Swift [duplicate]

This question already has answers here:
Disable UIPageViewController bounce
(16 answers)
Closed 5 years ago.
I've been making a sample app with scrolling between UIViewControllers but the point is i want to disable the bouncing effect at the end of scrolling and when going back to the first UIViewController.
here is my code :
class PageViewController: UIPageViewController,UIPageViewControllerDataSource, UIPageViewControllerDelegate{
var index = 0
var identifiers: NSArray = ["FirstNavigationController", "SecondNavigationController"]
override func viewDidLoad() {
self.dataSource = self
self.delegate = self
let startingViewController = self.viewControllerAtIndex(self.index)
let viewControllers: NSArray = [startingViewController]
self.setViewControllers(viewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
func viewControllerAtIndex(index: Int) -> UINavigationController! {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
//first view controller = firstViewControllers navigation controller
if index == 0 {
return storyBoard.instantiateViewControllerWithIdentifier("FirstNavigationController") as! UINavigationController
}
//second view controller = secondViewController's navigation controller
if index == 1 {
return storyBoard.instantiateViewControllerWithIdentifier("SecondNavigationController") as! UINavigationController
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
//if the index is the end of the array, return nil since we dont want a view controller after the last one
if index == identifiers.count - 1 {
return nil
}
//increment the index to get the viewController after the current index
self.index = self.index + 1
return self.viewControllerAtIndex(self.index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
//if the index is 0, return nil since we dont want a view controller before the first one
if index == 0 {
return nil
}
//decrement the index to get the viewController before the current one
self.index = self.index - 1
return self.viewControllerAtIndex(self.index)
}
}
Take a look here :
I don't know if you can. UIPageController is not very customizable.
Personally, when I want to scroll between UIViewController, I prefer using a simple UIViewController, which will be a container, with an UIScrollView in it. Then I add programmatically all the controllers in the contentSize of the UIScrollView. You have to add all the controllers as child of the container.
UPDATE iOS 9
func setupDetailViewControllers() {
var previousController: UIViewController?
for controller in self.controllers {
addChildViewController(controller)
addControllerInContentView(controller, previousController: previousController)
controller.didMoveToParentViewController(self)
previousController = controller
}
}
func addControllerInContentView(controller: UIViewController, previousController: UIViewController?) {
contentView.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
// top
controller.view.topAnchor.constraintEqualToAnchor(contentView.topAnchor).active = true
// bottom
controller.view.bottomAnchor.constraintEqualToAnchor(contentView.bottomAnchor).active = true
// trailing
trailingContentViewConstraint?.active = false
trailingContentViewConstraint = controller.view.trailingAnchor.constraintEqualToAnchor(contentView.trailingAnchor)
trailingContentViewConstraint?.active = true
// leading
let leadingAnchor = previousController?.view.trailingAnchor ?? contentView.leadingAnchor
controller.view.leadingAnchor.constraintEqualToAnchor(leadingAnchor).active = true
// width
controller.view.widthAnchor.constraintEqualToAnchor(scrollView.widthAnchor).active = true
}
PREVIOUS ANSWER
Like so :
scrollView is an IBOutlet with contraints to each edge of the ContainerViewController
class ContainerViewController: UIViewController {
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.bounces = false
scrollView.pagingEnabled = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidLayoutSubviews() {
initScrollView()
}
func initScrollView(){
let viewController1 = storyboard?.instantiateViewControllerWithIdentifier("ViewController1") as! ViewController1
viewController1.willMoveToParentViewController(self)
viewController1.view.frame = scrollView.bounds
let viewController2 = storyboard?.instantiateViewControllerWithIdentifier("ViewController2") as! ViewController2
viewController2.willMoveToParentViewController(self)
viewController2.view.frame.size = scrollView.frame.size
viewController2.view.frame.origin = CGPoint(x: view.frame.width, y: 0)
scrollView.contentSize = CGSize(width: 2 * scrollView.frame.width, height: scrollView.frame.height)
scrollView.addSubview(viewController2.view)
self.addChildViewController(viewController2)
viewController2.didMoveToParentViewController(self)
scrollView.addSubview(viewController1.view)
self.addChildViewController(viewController1)
viewController1.didMoveToParentViewController(self)
}}

Resources