UIPageViewController load ChildViews multiple times - swift

I've subclassed UIPageViewController, and it works fine. Except the fact that going back and forth load new instances of the pages, instead of (as i would expect) retrieving the instance it loaded previously.
Aren't the references supposed to be strong?
Following is my code.
Thank you for your help.
Pierrick
class CustomPageViewController: UIPageViewController, UIPageViewControllerDataSource {
var view1: FirstCustomViewController!
var view2: SecondCustomViewController!
var view3: ThirdCustomViewController!
var views = []
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self;
self.view1 = FirstViewController()
self.view2 = SecondViewController()
self.view3 = ThirdViewController()
self.views = [self.view1, self.view2, self.view3]
self.setViewControllers([self.view1], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
}
// MARK: - PageViewControllerDataSource
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let index = self.views.indexOfObject(viewController)
if (index > 0) {
return self.views.objectAtIndex(index-1) as? UIViewController
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let index = self.views.indexOfObject(viewController)
if (index < self.views.count-1) {
return self.views.objectAtIndex(index+1) as? UIViewController
}
return nil
}
}

Seems like the faulty function is in FirstViewController, SecondViewController, ThirdViewController. When i wrote my question, the view controller loaded the view following the method "viewWillAppear".
As soon as i moved the code to "viewDidLoad", the views stopped to load multiple times.

Related

Issue Implementing UIPageViewController

I am trying to implement to scroll between two view controllers, one to be a message page, and the other view controller to be a camera page, similar to snapchat app. If I didn't make myself understood I attached an image with the app that has exactly the same implementation as I'm trying to do, it has 5 views which you can scroll between them. image
Now I have tried it to implement in multiple ways. One of them is with UIPageViewController, snippet of my code:
class ViewController: UIViewController , UIPageViewControllerDelegate
{
var chatVC = one()
var cameraVC= two()
var array = [UIViewController]()
var arr = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
self.array = [chatVC,cameraVC]
self.arr = [chatVC]
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
self.presentPageVC()
})
}
func presentPageVC(){
var vc = UIPageViewController.init(transitionStyle: .scroll, navigationOrientation: .horizontal , options: nil)
vc.delegate = self
vc.dataSource = self
vc.setViewControllers(self.arr, direction: .forward, animated: true, completion: nil)
self.present(vc, animated: true, completion: nil)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = self.array.firstIndex(of: viewController),index>0 else {return nil}
let beforeIndex = index - 1
return self.array[beforeIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = self.array.firstIndex(of: viewController), index < (self.array.count-1) else {
return nil
}
var indexAfter = index + 1
return self.array[indexAfter]
}
With this implementation I have one problem as I try to scroll after the last page on the cameraVC, on the right it appears the view behind it. How could I turnOff scrolling when there is no other page after or before.
As well, Im new to software development, if somebody has a better idea for implementing it, please let me know. I get stuck at trying to implementing with UICollectionView because I don't know how to convert a UIViewController to a cell, and how to change depending on it on the delegate. As well when trying to add via UIScrollView, I don't understand how I could a UIViewController subview to the UIScrollview, because it accepts only UIView.
Thank you in advance for the help,
For anybody who wants the UIScrollView to don't show the viewUnder when scrolling there are no existent views to scroll.
scrollView.showsLargeContentViewer = false

UIPageViewController with only 1 UIViewController and infinite loop

First at all, I am not using UICollectionView because I need put my Slider inside of a ViewContainer and it's not allowed put repeatable elements in it.
As my question title says, I want to get work a UIPageViewController with only 1 UIViewController and infinite loop.
This is my current code:
class ListPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pages: [UIViewController] = []
let viewModel = ListPageViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.delegate = self
delegate = self
dataSource = self
if let storyboard = storyboard {
pages = [
storyboard.instantiateViewController(withIdentifier: "listViewController"),
storyboard.instantiateViewController(withIdentifier: "listViewController")
]
setViewControllers([pages.first!], direction: .forward, animated: true)
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
return brother(controller: viewController)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
return brother(controller: viewController)
}
func brother(controller: UIViewController) -> UIViewController? {
if controller == pages.first { return pages.last }
return pages.first
}
}
The problem is that I am not able to reference an unique UIViewController because I'm create two instead of one, and I need manipulate that child UIViewController from my UIPageViewController.
Some help?

UIViewControllerTransitioningDelegate on tvOS not being called

So I'm trying to do an animated transition between viewcontrollers on tvOS 10.
The UIViewControllerTransitioningDelegate protocol is available on tvOS so I assumed I could animate it as well. But for some reason none of the functions are ever called when presenting the new viewcontroller.
I have no idea what I'm doing wrong since I'm doing basically the exact same on iOS.
Here is the code I'm using:
Presenting code
func showStuff() {
let viewController = ResultViewController()
viewController.transitioningDelegate = ResultViewControllerTransitionManager()
viewController.modalPresentationStyle = .custom
navigationController?.present(viewController, animated: true, completion: nil)
}
Transition Delegate
class ResultViewControllerTransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
var duration = 0.5
var isPresenting = false
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {return}
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {return}
(...)
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isPresenting = false
return self
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isPresenting = true
return self
}
}
You can implement UIViewControllerAnimatedTransitioning and UIViewControllerTransitioningDelegate protocols inside your first view controller. Your first UIViewController may have the following code:
class ViewController: UIViewController, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning {
func showStuff() {
let viewController = ResultViewController()
viewController.transitioningDelegate = self
viewController.modalPresentationStyle = .custom
self.present(viewController, animated: true, completion: nil)
}
var duration = 0.5
var isPresenting = false
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 2.0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {return}
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {return}
}
}
Also, I present a new controller from the first one (not from the navigationController).
So apparently initialising the animationmanager in the showStuff() function was the problem. Storing it as a property in the Main viewcontroller and passing it along did work.
Wouldn't the issue be that you are using a navigationController, so that navigationController?.delegate needs to equal your ResultViewControllerTransitionManager()?
We have one at work using a push animation (should be the same issue), but we set
navigationController?.delegate = transitionDelegate

Delete Image from UIPageViewController

I have a PageViewController set up so that I can add photos from camera or library and the pageviewcontroller resets itself every time the imagepickercontroller is called so the pageviewcontrol updates and shows. It works perfectly (i think). What i know want to do is have the option to delete one of the photos then call the same reset.
The problem is, i can not get an accurate index value of the current image being displayed in the pageviewcontroller. I try to grab the index from the viewControllerBeforeViewController or AfterViewController or the pagecontrolleratindex... but the index does not follow correctly the way the pagecontrol is designed. I read about willtransitiontoviewcontrollers and didfinsihanimating, but those are not being called, i am thinking because i am getting the images from an array and not new view controllers.
My method to delete works, but the index is not accurate so even though the second image is shown the first image could be the one deleted.
Is there a good way to get the current displayed array index so i can delete the image from array? //this array holds images from imagepicker, i call the restartviewcontrollers evertime there is an imagepicked to update the number of pages and this works.
UPDATE
var pageViewController:UIPageViewController!
var pageImages: [UIImage]? = [] //this array holds images from imagepicker, i call the restartviewcontrollers evertime there is an imagepicked to update the number of pages and this works.
var deletePageIndex: Int = 0
func pageViewControllerAtIndex(index: Int) -> PhotoContentPageViewController {
if (self.pageImages!.count == 0 || index >= self.pageImages!.count) {
return PhotoContentPageViewController()
}
var vc = self.storyboard?.instantiateViewControllerWithIdentifier("PhotoPageContentStoryBoard") as! PhotoContentPageViewController
vc.imageFileName = pageImages![index] as! UIImage
vc.pageIndex = index
return vc
}
func restartViewController() {
var startVc = self.pageViewControllerAtIndex(0) as PhotoContentPageViewController
var viewControllers = NSArray(object: startVc)
self.pageViewController.setViewControllers(viewControllers as? [UIViewController], direction: .Forward, animated: true, completion: nil)
}
func startViewControllers() {
self.pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PhotoPageViewControllerStoryBoard") as! UIPageViewController
self.pageViewController.dataSource = self
pageViewController.delegate = self
var startVc = self.pageViewControllerAtIndex(0) as PhotoContentPageViewController
var viewControllers = NSArray(object: startVc)
self.pageViewController.setViewControllers(viewControllers as? [UIViewController], direction: .Forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, -10, self.view.frame.size.width, self.view.frame.size.height-30)
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var vc = viewController as! PhotoContentPageViewController
var index = vc.pageIndex as Int
if (index == 0 || index == NSNotFound) {
return nil
}
index--
return self.pageViewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var vc = viewController as! PhotoContentPageViewController
var index = vc.pageIndex as Int
if (index == NSNotFound) {
return nil
}
index++
if (index == self.pageImages!.count) {
return nil
}
return self.pageViewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [UIViewController]) {
}
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if (!completed) {
return
}
}
`
If you actually are using a UIPageViewControllerDelegate, you can use willtransitiontoviewcontrollers but that method only works if you set the delegate property of the page view controller to your view controller. That way, you can simply get the index of the current view controller in page view controller and use it for deletion.
myPageViewController.delegate = self
And make sure your view controller implements UIPageViewControllerDelegate
UPDATE
Assuming that you have an array of you view controllers, you could implement your data source like this:
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
if let index = pages.indexOf(viewController)?.advancedBy(1) where index < pages.count {
return pages[index]
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
if let index = pages.indexOf(viewController)?.advancedBy(-1) where index > -1 {
return pages[index]
}
return nil
}

Swift - Bringing UIPageViewController controls to front

I'm having some trouble bringing the dotted UIPageControls to the front after I filled out the whole screen.
Here's my current code:
import UIKit
class ViewController: UIViewController, UIPageViewControllerDataSource {
//This outlet wasn't originally there. Added a UIPageControl as an attempt to solve my issue
#IBOutlet var pageControl: UIPageControl!
var pageViewController : UIPageViewController?
var pageTitles : Array<String> = ["Orc", "Elven", "Undead"]
var pageImages : Array<String> = ["Plain_Orc.png", "Plain_Elf.png", "Plain_Undead.png"]
var currentIndex : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.pageViewController = UIPageViewController(transitionStyle: .Scroll, navigationOrientation: .Horizontal, options: nil)
self.pageViewController!.dataSource = self
let startingViewController: PageContentViewController = self.viewControllerAtIndex(0)!
let viewControllers: NSArray = [startingViewController]
self.pageViewController!.setViewControllers(viewControllers, direction: .Forward, animated: false, completion: nil)
self.pageViewController!.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height + 37);
self.addChildViewController(self.pageViewController!)
//self.view.addSubview(self.pageViewController!.view)
//replacing addSubview from insane-36
self.view.insertSubview(pageViewController!.view, belowSubview: self.pageControl!)
self.pageViewController!.didMoveToParentViewController(self)
//Bring pagecontrols to front? (DOsESN'T WORK)
//self.view.bringSubviewToFront(self.pageViewController!.view)
//attempt to fix above code from insane-36
view.bringSubviewToFront(self.pageControl!)
view.sendSubviewToBack(self.pageViewController!.view)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
var index = (viewController as PageContentViewController).pageIndex
if (index == 0) || (index == NSNotFound) {
return nil
}
index--
println("Decreasing Index: \(String(index))")
return self.viewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
{
var index = (viewController as PageContentViewController).pageIndex
if index == NSNotFound {
return nil
}
index++
println("Increasing Index: \(String(index))")
if (index == self.pageTitles.count) {
return nil
}
return self.viewControllerAtIndex(index)
}
func viewControllerAtIndex(index: Int) -> PageContentViewController?
{
if self.pageTitles.count == 0 || index >= self.pageTitles.count
{
return nil
}
// Create a new view controller and pass suitable data.
let pageContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PageContentViewController") as PageContentViewController
pageContentViewController.imageFile = self.pageImages[index]
pageContentViewController.titleText = self.pageTitles[index]
pageContentViewController.pageIndex = index
self.currentIndex = index
return pageContentViewController
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int
{
return self.pageTitles.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int
{
return 0
}
}
I thought the line that brings the pageViewController view to front would fix my issue, but it doesn't work. Hopefully someone can give me a push in the right direction :)
Use any one of the following method to send the UIPageViewController's view to the back of the UIPageControl,
Instead of using addSubview: to add UIPageViewController's
view, use
view.insertSubview(pageViewController.view, belowSubview:self.pageControl)
Use bringSubviewToFront: method,
view.bringSubviewToFront(self.pageControl) // after you have added the UIPageViewController's view
Use sendSubviewToBack: method,
view.sendSubviewToBack(self.pageViewController.view)