call func in parent UIViewController from last modal UINavigatonController UIViewController - swift

i present a model UINavigationController like so
let flowLayout = UICollectionViewFlowLayout()
let firstViewController = FirstViewController(collectionViewLayout:flowLayout)
let navigationController = UINavigationController(rootViewController: firstViewController)
navigationController.modalPresentationStyle = .fullScreen
present(navigationController, animated: true, completion: nil)
this NavigationController will contain two UIViewcontrollers,
in the last one, when I dismiss the NavigationController, i want to call a function in my main controller before dismissing
I know how to this using protocols and delegates, but only if i use just two UIViewController not a UIViewController and a UINavigationController.
like so
protocol SecondViewControllerDelegate {
func someFunction()
}
class SecondViewController: UIViewController {
var delegate: SecondViewControllerDelegate?
#objc func myRightSideBarButtonItemTapped(_ sender:UIBarButtonItem!)
{
self.delegate?.someFunction()
}
}
do I have to create a CustomNavigationController, or is there any other way like passing the delegate through all ViewControllers

You can write something like:
let flowLayout = UICollectionViewFlowLayout()
let firstViewController = FirstViewController(collectionViewLayout: flowLayout)
firstViewController.delegate = self
let navigationController = UINavigationController(rootViewController: firstViewController)
navigationController.modalPresentationStyle = .fullScreen
present(navigationController, animated: true, completion: nil)
or using callback instead of delegate:
class FViewController: UIViewController {
var onButtonAction: (() -> Void)?
#IBAction onButtonTapped(_ sender: Any) {
onButtonAction?()
}
}
class SViewController: UIViewController {
func someMethod() {
let fVC = FViewController()
fVC.onButtonAction = {}
let navigationController = UINavigationController(rootViewController: fVC)
present(navigationController, animated: true, completion: nil)
}
}

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.

Swift NavigationController push method request doesn't work from PopUp view

The new PopUp ViewController is presented .overCurrentContext
There are 2 buttons with navigation for 2 other views from PopUp view.
A simple action such as print goes well, but when I try to segue programmatically (from xib file, popUpViewController) nothing happens.
let vc = RegisterEmailViewController.instantiate()
vc.coordinator = self
navigationController.pushViewController(vc, animated: false)
Goes well from any other view.
What could be wrong?
PopUpViewController code:
// Created by ᴀʟᴇxᴀɴᴅʀ ᴢʜᴇʟɪᴇᴢɴɪᴀᴋ on 23.12.2019.
// Copyright © 2019 ᴀʟᴇxᴀɴᴅʀ ᴢʜᴇʟɪᴇᴢɴɪᴀᴋ. All rights reserved.
import UIKit
class ViewController: UIViewController, Storyboarded {
weak var coordinator: MyCoordinator?
#IBAction func dismissPopUp(_ sender: Any) {
dismiss(animated: false, completion: nil)
}
#IBAction func buttonEmail(_ sender: Any) {
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "EmailViewController") as? EmailViewController
self.navigationController?.pushViewController(vc!, animated: true)
}
#IBAction func buttonPhone(_ sender: Any) {
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "PhoneViewController") as? PhoneViewController
self.navigationController?.pushViewController(vc!, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
There is MyCoordinator which inits all navigations and doesn't let me simply to segue/navigate by my own.
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
Therefore, a reference to the controller solved the issue. It is possible to navigate aside, until new subViews, separate views are called.
Code that worked in this case:
let vc = RegisterEmailViewController.instantiate()
vc.coordinator = self
navigationController.pushViewController(vc, animated: false)
Conclusion. Simple there are two ways:
To delete all dependencies and other methods which use
UINavigationController
To declare only by new methods for global usage.

iOS 13 - Right Bar Button Item goes offset in Modal Sheet presentation

I have this strange problem with iOS 13 and its new sheet cards style modal presentation.
From ViewController1 I modal present ViewController2 embedded in a NavigationController, and everything works fine.
From ViewController2, I then modal present ViewController3 embedded in a NavigationController, and I get the Right Bar Button offset.
Here is a video of the problem: does anybody have a fix?
Main View Controller
import UIKit
let vc1identifier = "vc1identifier"
let vc2identifier = "vc2identifier"
class ViewController: UIViewController {
#IBAction func tap1(_ sender: UIButton) {
if let navigation = self.storyboard?.instantiateViewController(withIdentifier: vc1identifier) as? UINavigationController,
let nextVC = navigation.contentViewController as? UnoViewController {
//self.navigationController?.pushViewController(nextVC, animated: true)
self.present(navigation, animated: true, completion: nil)
}
}
}
extension UIViewController {
var contentViewController: UIViewController {
if let navcon = self as? UINavigationController {
return navcon.visibleViewController!
} else {
return self
}
}
}
Second View Controller
import UIKit
class UnoViewController: UIViewController {
#IBOutlet weak var barButton: UIBarButtonItem!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
barButton.title = "GotoVC2"
}
#IBAction func pressThis(_ sender: UIBarButtonItem) {
if let navigation = self.storyboard?.instantiateViewController(withIdentifier: vc2identifier) as? UINavigationController,
let nextVC = navigation.contentViewController as? DueViewController {
self.present(navigation, animated: true, completion: nil)
}
}
}
I came across the same issue.
Solution is easy, you just need to tell navigationbar it needs layout like this
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 13.0, *) {
navigationController?.navigationBar.setNeedsLayout()
}
}

How do i load a viewcontroller from a xib file?

I tried to load a viewcontroller from a custom cell using delegates.But i get nil from the set delegate!
Here is a sample if anyone can help!
1. In Cell
protocol hotelFindDelegate{
func modalDidFinished(modalText: String)
}
class hotelFindCell: UITableViewCell {
var delegate:hotelFindDelegate?
#IBAction func findButton(sender: AnyObject) {
self.delegate!.modalDidFinished("HELLO")
print("Damn nothing works")
}
2. In Main View
class MainViewController:hotelFindDelegate {
let modalView = hotelFindCell()
override func viewDidLoad() {
super.viewDidLoad()
self.modalView?.delegate = self
}
func modalDidFinished(modalText: String){
let viewController:UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("HotelListVC") as UIViewController
self.presentViewController(viewController, animated: false, completion: nil)
self.modalView.delegate = self
print(modalText)
}
To load view controller from XIB do the following steps.
let settingVC : SettingsViewController = SettingsViewController(nibName :"SettingsViewController",bundle : nil)
later on you can push the same view controller object like
self.navigationController?.pushViewController(settingsVC, animated: true)
NSBundle.mainBundle().loadNibNamed("viewController", owner: self, options: nil)

How add tabs programmatically in UITabBarController with swift?

How to create programmatically tabs from any class extended by UIViewController:
class DashboardTabBarController: UITabBarController {
override func viewDidLoad() {
//here
}
...
}
UPDATE SWIFT 5
One example of how to create an UITabBarController programmatically could be like this:
First we create the UIViewControllers that will be the content for each tab of the tab bar interface. For this example we only create one very simple.
class Item1ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.green
self.title = "item1"
print("item 1 loaded")
}
}
Now, the UITabBarController:
We create the new instances of the UIViewControllers that we want to display in the tab bar. Then we create an icon for each instance we have created and then we create an array that contains all UIViewControllers that specify the content for each tab of the tab bar interface. The order of the view controllers in the array corresponds to the display order in the tab bar.
class DashboardTabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let item1 = Item1ViewController()
let icon1 = UITabBarItem(title: "Title", image: UIImage(named: "someImage.png"), selectedImage: UIImage(named: "otherImage.png"))
item1.tabBarItem = icon1
let controllers = [item1] //array of the root view controllers displayed by the tab bar interface
self.viewControllers = controllers
}
//Delegate methods
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
print("Should select viewController: \(viewController.title ?? "") ?")
return true;
}
}
If you are using storyboard for the viewcontrollers then you have to write like this in your tabbarcontroller class.
class CustomTabbarController : UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let firstViewController = FirstViewController()
let navigationController = UINavigationController(rootViewController: firstViewController)
navigationController.title = "First"
navigationController.tabBarItem.image = UIImage.init(named: "map-icon-1")
viewControllers = [navigationController]
if let secondViewController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController {
let navgitaionController1 = UINavigationController(rootViewController: secondViewController)
navgitaionController1.title = "Second"
navgitaionController1.tabBarItem.image = UIImage.init(named: "second-icon-1")
var array = self.viewControllers
array?.append(navgitaionController1)
self.viewControllers = array
}
}
}
private lazy var tabbarViewController: UITabBarController = {
let tabbarViewController = UITabBarController()
tabbarViewController.setViewControllers([startVC,
offerVC,
benefitsVC,
shopVC,
recipesVC], animated: true)
return tabbarViewController
}()
window?.rootViewController = tabbarViewController