Issue Implementing UIPageViewController - swift

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

Related

How to set the PageViewController to cover the whole screen and not be modal?

I am implementing a UIPageViewController to my app to try to build a UI like Tinder, in which you can swipe left and right to not only like or dislike a person, but to navigate different screens, i.e. chat screen, profile screen, matches screen etc.
In my case, after signing in, a UIPageViewController that contains 4 other UIViewControllers will pop up.
However, the UIPageViewController is modal and doesn't cover the whole screen(as there is a small gap at the top which allows the user to swipe the modal down and away).
I tried using code like this:
self.window = UIWindow(frame: UIScreen.main.bounds)
if let window = self.window {
window.rootViewController = PageViewController()
window.makeKeyAndVisible()
}
at my AppDelegate, or setting Full Screen at the storyboard, but did't work.
I wonder how I should do this?
Or maybe UIPageViewController is not the right choice to achieve this swipe from screen to screen navigation style Tinder has?
Anyway, here is the code of my PageViewController:
import UIKit
class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var controllers = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
let vc = TodayPicksViewController()
controllers.append(vc)
let vc1 = TopPicksViewController()
vc1.view.backgroundColor = .yellow
controllers.append(vc1)
let vc2 = ChatViewController()
vc2.view.backgroundColor = .gray
controllers.append(vc2)
let vc3 = (storyboard?.instantiateViewController(withIdentifier: String(describing: ProfileViewController.self)) as? ProfileViewController)!
controllers.append(vc3)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now()+2, execute: {
self.presentPageVC()
})
}
func presentPageVC() {
guard let first = controllers.first else {
return
}
let vc = UIPageViewController(transitionStyle: .scroll,
navigationOrientation: .horizontal,
options: nil)
vc.delegate = self
vc.dataSource = self
vc.setViewControllers([first],
direction: .forward,
animated: true,
completion: nil)
present(vc, animated: true)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = controllers.firstIndex(of: viewController), index > 0 else {
return nil
}
let before = index - 1
return controllers[before]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = controllers.firstIndex(of: viewController), index < (controllers.count - 1) else {
return nil
}
let after = index + 1
return controllers[after]
}
}
By default when you present a ViewController in Swift it doesn't cover the fullscreen. To make it cover the fullscreen you need to set the modalPresentationStyle on the ViewController.
So in your presentPageVC method you need to add the following line :
vc.modalPresentationStyle = .fullScreen
So your method will now look like this:
func presentPageVC() {
guard let first = controllers.first else {
return
}
let vc = UIPageViewController(transitionStyle: .scroll,
navigationOrientation: .horizontal,
options: nil)
vc.delegate = self
vc.dataSource = self
vc.setViewControllers([first],
direction: .forward,
animated: true,
completion: nil)
vc.modalPresentationStyle = .fullScreen // <- add this before presenting your ViewController
present(vc, animated: true)
}
To read more about the different presentation styles that you can have check out the documentation here.

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?

Swift: NSTextfield and popover with suggested list of tags

I'm trying to do something like this:
This is what I have now:
How I'm doing this now:
I have MainViewController with input field: NSTextField.
Using controlTextDidChange I'm checking if input field contains # character.
If yes - I'm opening popover using PopoverViewController.
In the PopoverViewController I have NSTableView that displays a list of available tags.
By clicking on the row it should add selected tag to the input fields and close the popover.
So here I even have to implement 2 way of communication between 2 view controllers.
Questions:
I feel that I'm doing something wrong. The logic of this tags-popover looks too complicated for me. Maybe an easier solution exists, but I just don't know how to do this.
What the best way to display the list of available tags in NSTextField? Should I use a popover with another view controller?
How to communicate best in this case between 2 different view controllers? (I have to send InputField value to PopoverViewController, filter results there and display them. Then, I have to send back to MainViewController selected tag)
My code:
Here is some parts of my code if you want to see it in more details:
MainViewController:
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet weak var InputField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
InputField.delegate = self
}
func controlTextDidChange(_ sender: Notification) {
if InputField.stringValue.contains("#") {
let controller = ToolTipVC.Controller()
let popover = NSPopover()
popover.contentViewController = controller
popover.behavior = .transient
popover.animates = true
popover.show(relativeTo: InputField.bounds, of: InputField!, preferredEdge: NSRectEdge.maxY)
}
}
ToolTipViewController:
import Cocoa
import EventKit
class ToolTipVC: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var ToolTipTable: NSTableView!
override func viewDidLoad() {
super.viewDidLoad()
ToolTipTable.delegate = self
ToolTipTable.dataSource = self
}
func numberOfRows(in tableView: NSTableView) -> Int {
return ReminderLists.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let cell = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? NSTableCellView else { return nil }
cell.textField?.stringValue = "Row #\(row): " + ReminderLists[row].title
return cell
}
}
extension ToolTipVC {
static func Controller() -> ToolTipVC {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let identifier = NSStoryboard.SceneIdentifier("ToolTipId")
guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? ToolTipVC else {
fatalError("Why cant i find viewcontroller? - Check Main.storyboard")
}
return viewcontroller
}
}
I appreciate your feedback and advises.
Thank you in advance!
P.S.: I'm not a developer, I'm a Product Manager. This is my pet-project, so I know that my code is awful and has a lot of mistakes.
Please, don't judge me hard :)
At first glance it looks like when you call this line:
popover.show(relativeTo: InputField.bounds, of: InputField!, preferredEdge: NSRectEdge.maxY)
preferredEdge is set to NSRectEdge.maxY
Try changing this from Y to X
popover.show(relativeTo: InputField.bounds, of: InputField!, preferredEdge: NSRectEdge.maxX)
Is there a reason you are usin NSPopover?
If that doesnt work try this:
let controller = MyPopViewController()
controller.modalPresentationStyle = UIModalPresentationStyle.popover
let popController = controller.popoverPresentationController
popController?.permittedArrowDirections = .any
popController?.delegate = self
popController?.sourceRect = //set the frame here
popController?.sourceView = //set the source view here
self.present(controller, animated: true, completion: nil)
Here is another approach, this will centre the pop over over a button. Which is what you are trying to do i am assuming with no up arrow? Is that correct?
So you can hit the filters button on the right side whihc you have and this pop over will appear centred.
#IBAction func buttonTap(sender: UIButton) {
// get a reference to the view controller for the popover
let popController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "popoverId")
// set the presentation style
popController.modalPresentationStyle = UIModalPresentationStyle.popover
// set up the popover presentation controller
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
popController.popoverPresentationController?.delegate = self
popController.popoverPresentationController?.sourceView = sender // button
popController.popoverPresentationController?.sourceRect = sender.bounds
// present the popover
self.present(popController, animated: true, completion: nil)
}

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
}

UIPageViewController load ChildViews multiple times

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.