How to Create Viewcontrollers Without Storyboard and Set One as Delegate of The Other One

How to create ViewControllers without storyboard and set one as delegate of the other one?

Note: My previous answer was using Storyboard. But since the questioner didn't want to use storyboard, I replace my answer without using storyboard.
[ this answer was inspired by https://stackoverflow.com/a/41095757/3549695 ]

First, delete the Main.storyboard. Then in Project -> Deployment Info -> Main Interface (pick LaunchScreen instead of 'Main')Sample Image

Then on AppDelegate.swift modify didFinishLaunching with the following:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

window = UIWindow(frame: UIScreen.main.bounds)
let discoverVC = DiscoverVC() as UIViewController
let navigationController = UINavigationController(rootViewController: discoverVC)
navigationController.navigationBar.isTranslucent = false
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()

return true
}

The DiscoverVC.swift looks like this:

import UIKit

class DiscoverVC: UIViewController, SetLocationDelegate {

var name = ""

// to instantiate LocationVC once only for testing
var notVisted = true

override func viewDidLoad() {
super.viewDidLoad()

self.view.backgroundColor = .yellow
loadLocationVCOnlyOnce()
}

func loadLocationVCOnlyOnce() {
// only visit one
guard notVisted else { return }

let locationVC = LocationVC()
locationVC.delegate = self
self.navigationController?.pushViewController(locationVC, animated: true)

}

func getLocation(loc: String) {
self.name = loc
print(name)
}
}

And the LocationVC looks like this:

import UIKit

protocol SetLocationDelegate: class {
func getLocation(loc: String)
}

class LocationVC: UIViewController {

weak var delegate: SetLocationDelegate?

override func viewDidLoad() {
super.viewDidLoad()

self.view.backgroundColor = .cyan
self.delegate?.getLocation(loc: "Sam")

}

}

When you start it will automatically move from DiscoverVC (yellow background) to LocationVC (cyan background).

Then after you click the 'Back' button on top, you will see the 'Sam' printed in your console. And your view returned to DiscoverVC (yellow background).

Swift – Instantiating a navigation controller without storyboards in App Delegate

In Swift 3

Place this code inside didFinishLaunchingWithOptions method in AppDelegate class.

window = UIWindow(frame: UIScreen.main.bounds)
let mainController = MainViewController() as UIViewController
let navigationController = UINavigationController(rootViewController: mainController)
navigationController.navigationBar.isTranslucent = false
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()

Xcode 5 - iOS - How to Create a View Controller in Xcode without Storyboard or XIB and how to configure it into AppDelegate as the root view

In AppDelegate.m:

CustomViewController *rootViewController = [CustomViewController new];

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];

In CustomViewController.m override:

- (void)loadView {
//Configure your view
self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

//other stuff
[self.view setBackgroundColor:[UIColor redColor]];

UIButton *button = /* alloc init etc. */
[self.view addSubview:button];
}

push ViewController without storyboard

Inside AppDelegate's didFinishLaunchingWithOptions do

let fir = FirstVC()
self.window?.rootViewController = UINavigationController(rootViewController: fir)

Then this

self.navigationController?.pushViewController(vc, animated: true)

should work

How to open view controllers in a container view without storyboard

Let's assume we have four view controllers: RedViewController, GreenViewController, BlueViewController, and the one to contain them all, ContainerViewController.

Although you mentioned a scrolling view controller with three children within, we'll make it a two screen setup to keep it simple.
The following approach is scalable, so you would easily adopt it with an arbitrary number of view controllers.

Our RedViewController is 7 lines long:

class RedViewController: UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .red
self.view = view
}
}

Before we move on to GreenViewController and BlueViewController, we will define protocol SwapViewControllerDelegate:

protocol SwapViewControllerDelegate: AnyObject {
func swap()
}

GreenViewController and BlueViewController will have a delegate that conforms to this protocol, which will handle the swapping.
We will make ContainerViewController conform to this protocol.

Note that SwapViewControllerDelegate has the AnyObject in its inheritance list to make it a class-only protocol–we can thus make the delegate weak, to avoid memory retain cycle.

The following is GreenViewController:

class GreenViewController: UIViewController {
weak var delegate: SwapViewControllerDelegate?

override func loadView() {
let view = UIView()
view.backgroundColor = .green
self.view = view
}

override func viewDidLoad() {
super.viewDidLoad()

let button = UIButton()
button.setTitle("Swap Me!", for: .normal)
button.setTitleColor(.black, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 50)
button.addTarget(
self,
action: #selector(swapButtonWasTouched),
for: .touchUpInside)

view.addSubview(button)

// Put button at the center of the view
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}

@objc private func swapButtonWasTouched(_ sender: UIButton) {
delegate?.swap()
}
}

It has weak var delegate: SwapViewControllerDelegate? which will handle the swap when the button added in viewDidLoad is touched, triggering the swapButtonWasTouched method.

BlueViewController is implemented likewise:

class BlueViewController: UIViewController {
weak var delegate: SwapViewControllerDelegate?

override func loadView() {
let view = UIView()
view.backgroundColor = .blue
self.view = view
}

override func viewDidLoad() {
super.viewDidLoad()

let button = UIButton()
button.setTitle("Swap Me!", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 50)
button.addTarget(
self,
action: #selector(swapButtonWasTouched),
for: .touchUpInside)

view.addSubview(button)

// Put button at the center of the view
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}

@objc private func swapButtonWasTouched(_ sender: UIButton) {
delegate?.swap()
}
}

The only difference is the view's backgroundColor and the button's titleColor.

Finally, we'll take a look at ContainerViewController.
ContainerViewController has four properties:

class ContainerViewController: UIViewController {

let redVC = RedViewController()
let greenVC = GreenViewController()
let blueVC = BlueViewController()

private lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.bounces = false
scrollView.isPagingEnabled = true
return scrollView
}()
...
}

scrollView is the view that will contain child view controllers, redVC, greenVC, and blueVC.
We will use autolayout, so don't forget to mark translatesAutoresizingMaskIntoConstraints as false.

Now, setup autolayout constraints of the scrollView:

class ContainerViewController: UIViewController {
...
private func setupScrollView() {
view.addSubview(scrollView)
let views = ["scrollView": scrollView]
[
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[scrollView]|",
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[scrollView]|",
metrics: nil,
views: views),
]
.forEach { NSLayoutConstraint.activate($0) }
}
...
}

I used VFL, but you can manually set autolayou constraints as we did for the button above.
Using autolayout, we don't have to set contentSize of the scrollView ourselves.
For more information about using autolayout with UIScrollView, see Technical Note TN2154: UIScrollView And Autolayout.

Now the most important setupChildViewControllers():

class ContainerViewController: UIViewController {
...
private func setupChildViewControllers() {
[redVC, greenVC, blueVC].forEach { addChild($0) }

let views = [
"redVC": redVC.view!,
"greenVC": greenVC.view!,
"blueVC": blueVC.view!,
]
views.values.forEach {
scrollView.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
$0.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
$0.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}

[
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[redVC][greenVC]|",
options: .alignAllTop,
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[redVC][blueVC]|",
options: .alignAllTop,
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[redVC(==greenVC,==blueVC)]|",
metrics: nil,
views: views),
]
.forEach { NSLayoutConstraint.activate($0) }

[redVC, greenVC, blueVC].forEach { $0.didMove(toParent: self) }

greenVC.view.isHidden = true

greenVC.delegate = self
blueVC.delegate = self
}
...
}

We first add each of [redVC, greenVC, blueVC] as child view controllers of ContainerViewController.
Then add the view's of child view controllers to scrollView.
Set widthAnchor and heightAnchor of the child view controllers to be view.widthAnchor and view.heightAnchor, in order to make them fullscreen.
Moreover, this will also work when the screen rotates.

Using views dictionary, we use VFL to set autolayout constraints.
We will put greenVC.view on the right of redVC.view: H:|[redVC][greenVC]|, and similarly for the blueVC.view: H:|[redVC][blueVC]|.
To fix the vertical position of greenVC.view and blueVC.view, add .alignAllTop option to the constraints.
Then apply vertical layout for redVC.view, and set the height of the greenVC.view and blueVC.view: "V:|[redVC(==greenVC,==blueVC)]|.
The vertical position is set, as we used .alignAllTop while setting the horizontal constraints.

We should call didMove(toParent:) methods on the child view controllers after we add then as child view controllers.
(If you are wondering about what didMove(toParent:) and addChild(_:) methods do, apparently they do very little; see What does addChildViewController actually do? and didMoveToParentViewController and willMoveToParentViewController.)

Finally, hide greenVC.view, and set greenVC.delegate and blueVC.delegate to self.
Then of course, we need ContainerViewController to conform to SwapViewControllerDelegate:

extension ContainerViewController: SwapViewControllerDelegate {
func swap() {
greenVC.view.isHidden.toggle()
blueVC.view.isHidden.toggle()
}
}

That's it!
The entire project is uploaded here.

I recommend reading Implementing a Container View Controller, which is well-documented by Apple. (It is written in Objective-C, but it is actually straightforward to translate into Swift)

Making a Delegate Call from One View Controller to Another

You need to override prepareForSegue in 1st viewcontroller for passing 1st viewcontroller ref to secondviewcontroller delegate property.

override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
let viewController = segue.destinationViewController as! SettingsViewController
viewController.delegate = self
}

In HomeViewController viewDidLoad code doesn't make any sense because you're performing segue from storyboard.

UPDATED

@IBAction func rightTapped(sender: AnyObject) {
let storyboard = UIStoryboard(name: "Settings", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("InitialController") as! UINavigationController
let view = controller.topViewController as! SettingsViewController
view.delegate = self

view.selectedRow = lineSelection
self.navigationController?.pushViewController(view, animated: true)
}

and you can directly get the SettingViewController ref. just set viewcontroller storyboard id and get it like this

 let storyboard = UIStoryboard(name: "Settings", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("InitialController") as! UINavigationController

let controller = storyboard.instantiateViewController(withIdentifier: "SettingsViewController") as! SettingsViewController

Delegate pattern without storyboard or segue

The answer from @PhillipMills should be correct, just to add some notes about naming conventions from this and this for you to get better code quality.

  • You should use uppercase for types (and protocols), lowercase for everything else
  • No need to inherit from NSObject unless you want something from the ObjC world, whether working with ObjC or use KVO

Setting delegate of another class with screen view to self

Because you try to learn how to build a delegate in Swift, I have written you a plain delegate example below

protocol SecondViewControllerDelegate {
func didReceiveInformationFromSecondViewcontroller (information: String)
}

class ViewController: UIViewController, SecondViewControllerDelegate {

func openSecondViewController () {
if let secondViewControllerInstance: SecondViewController = storyboard?.instantiateViewControllerWithIdentifier("SecondViewController") as? SecondViewController {
secondViewControllerInstance.delegate = self
navigationController?.pushViewController(secondViewControllerInstance, animated: true)
}
}

func didReceiveInformationFromSecondViewcontroller(information: String) {
////Here you get the information, after sendInfoToViewController() has been executed
}

}

class SecondViewController: UIViewController {

var delegate: SecondViewControllerDelegate?

func sendInfoToViewController () {
delegate?.didReceiveInformationFromSecondViewcontroller("This ist the information")
}

}

UPDATE

Following the same thing in using Storyboard Segues

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let secondViewControllerInstance: SecondViewController = segue.destinationViewController as? SecondViewController {
secondViewControllerInstance.delegate = self
}
}


Related Topics



Leave a reply



Submit