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')
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
How to Create Generic Closures in Swift
Swift 3/4 Dash to Camel Case (Snake to Camelcase)
Swift: Guard Let and Where - The Priority
Show Status Bar Only for iPhone X
Why Is This Predicate Format Being Turned into '= Nil'
How to Get a Full List of Running Processes
Uibutton Borders Function Only Gives Back White Borders
Adding Items to The Dock Menu from My View Controller in My Cocoa App
Images Being Flipped When Adding to Nsattributedstring
Codable Nsmanagedobject Fail on Decodeifpresent Data Type
Proper Way of Editing a Cocoapod Library
Solve Equations of Type A*X = B Using Dgtsv_ or Sgtsv_
How to Make First Responder in Swiftui Macos
How to Make UItabbar Image Rounded in Swift Programmatically