about initializing of viewcontroller (swift) - ios

I have a ViewController file called TwoViewController.swift and a nib file called TwoViewController.xib.
TwoViewController.swift like this ↓
class TwoViewController: UIViewController {
var pageTitle: String?
・・・・・・
override func viewDidLoad() {
super.viewDidLoad()
}
・・・・・・
}
then, I would new a TwoViewController and present it at OneViewController.swift like this↓
class OneViewController: UIViewController {
・・・・・・
override func viewDidLoad() {
super.viewDidLoad()
}
・・・・・・
func presentTwo() {
let two = new TwoViewController()
two.pageTitle = "2222"
self.present(two, animated: false, completion: nil)
}
}
But, I want to new TwoViewController and set value to property pageTitle at the same time like this ↓
new TwoViewController(pageTitle: "22222")
To do that, I think I need create an init method at TwoViewController.
I tried to make an init method like below↓. Is this correct?
class TwoViewController: UIViewController {
var pageTitle: String
init(pageTitle: String) {
super.init(nibName: nil, bundle: nil)
self.pageTitle = pageTitle
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
}
・・・・・・
}

You could do so, but then you'd have to initialize pageTitle in every initializer with some default value, which you typically don't know.
Therefore, this is not quite common to do so. Instead assign the property value after initialization, like you did originally (in funcTwo), and go on with processing in viewDidLoad:
class TwoViewController: UIViewController {
var pageTitle: String!
override func viewDidLoad() {
// use pageTitle to fill some outlet or so:
self.title = pageTitle
}
}
or make pageTitle an optional and check in viewDidLoad if it is set.
By the way: If you follow the naming scheme and name your XIB file like your view controller, you could use the implicit form:
let twoController = TwoViewController.init()
or explicitly
let twoController = TwoViewController.init(nibName: "TwoViewController", bundle: nil)

you should initialise your TwoViewController from your nib file like this :
let twoController = TwoViewController.init(nibName: "TwoViewController", bundle: nil)
then you can initialise your pageTitle like this :
twoController.pageTitle = "2222"
then you can present your twoViewController like this :
self.present(twoController, animated: false, completion: nil)

Related

Is it a good way to pass data to custom view then execute the function?

I created a custom input accessory view, it is the submit button.
However, I need to pass the data to the custom view then execute the further function. It is a good way to do that?
class SignUpViewController: UIViewController {
#IBOutlet weak var phoneTF: SignLogTextField!
#IBOutlet weak var EmailTF: SignLogTextField!
#IBOutlet weak var PasswordTF: SignLogTextField!
#IBOutlet weak var FBBtn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
textFieldPreparation()
}
func textFieldPreparation(){
EmailTF.inputAccessoryView = Bundle.main.loadNibNamed("SignSubmitBTN", owner: self, options: nil)?.first as! SignSubmitBTN
phoneTF.inputAccessoryView = Bundle.main.loadNibNamed("SignSubmitBTN", owner: self, options: nil)?.first as! SignSubmitBTN
PasswordTF.inputAccessoryView = Bundle.main.loadNibNamed("SignSubmitBTN", owner: self, options: nil)?.first as! SignSubmitBTN
}
}
I am not sure how to pass the data to the custom view or should I do the sign up in the Outlet Action?
It is my custom view
import UIKit
class SignSubmitBTN: UIView {
#IBAction func submitAction(_ sender: Any) {
}
#IBOutlet weak var subBTN: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup(){}
}
If I have to pass data to custom view should I use protocol? If I should use the protocol of how to use it?
OK...
I think you are approaching this from the wrong direction. The responsibility of a button should be to tell you that a user has tapped it and nothing more. The button should not be dealing with signing in.
But... you are 90% of the way there here. Just a few more bits to add.
You can update your submit button to include a delegate and use the delegate in your button action...
import UIKit
// protocol
protocol SignInButtonDelegate: class {
func signIn()
}
class SignSubmitBTN: UIView {
// property for delegate
weak var delegate: SignInButtonDelegate?
#IBAction func submitAction(_ sender: Any) {
// this tells the delegate to sign in
// it doesn't need to know how that happens
delegate?.signIn()
}
#IBOutlet weak var subBTN: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {}
}
Then in your view controller you conform to the delegate protocol...
extension SignUpViewController: SignInButtonDelegate {
func signIn() {
// here you already have access to all the data you need to sign in.
// you are in the view controller here so just get the text from the username, password, etc...
}
}
And then set the view controller as the delegate...
func textFieldPreparation() {
let signInButton = Bundle.main.loadNibNamed("SignSubmitBTN", owner: self, options: nil)?.first as! SignSubmitBTN
signInButton.delegate = self
// these are properties... they should begin with a lowercase letter
emailTF.inputAccessoryView = signInButton
phoneTF.inputAccessoryView = signInButton
passwordTF.inputAccessoryView = signInButton
}
Your CustomView is just a class at the end, so you can do it in object oriented paratime, For that write a function in your customView to pass data in it. Like
class SignSubmitBTN: UIView {
var data: String!;
public func setData(data: String) {
self.data = data;
}
/// Other code
}
And to set data after initializing your CustomView, call setData(params) function to set data in it.
Try this
func loadFromNib() -> SignSubmitBTN {
let bundle = Bundle.main.loadNibNamed("SignSubmitBTN", owner: self, options: nil)?.first as! SignSubmitBTN
return bundle
}
In your viewcontroller call like below:
let customObj = loadFromNib()
customObj.dataToGet = "Data to pass"
customObj.delegate = self
EmailTF.inputAccessoryView = customObj
If you want pass data from custom class, You need to use delegate protocol as #Fogmeister suggested.
If you want delegate option
public protocol menuOpen: class {
func openMenuAction(selectedValue : String)
}
class SignSubmitBTN: UIView {
open var delegate:menuOpen?
var dataToGet = ""
#IBAction func submitAction(_ sender: Any) {
self.delegate.openMenuAction("test")
}
}
Then add delegate method in your VC
class SignUpViewController: UIViewController,menuOpen{
func openMenuAction(selectedValue : String) {
//get your selected value here, you would better pass parameter in this method
}
}

Variable is set but prints out nil

I am setting a variable in another viewcontroller and it is working inside didSet but in viewdidload it prints out nil
ViewController 1
let methodView = MethodViewController()
methodView.items = itemsSelected
presentDetail(newVC)
ViewController 2
class MethodViewController: UIViewController {
var items: [[String: Any]]? {
didSet {
print(items) // PRINTS OUT ITEMS NORMALLY
}
}
override func viewDidLoad() {
super.viewDidLoad()
print(items) // PRINTS OUT NIL
}
}
it is returning nil because you are setting the items by using one instance and when you present the screen and display your methodViewController. then you pass a new instance for same. instead set the items by using the same instance which you are using to present your controller
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let methodView = StackProgramViewController() //instance 1 //your case methodView
methodView.items = itemsSelected //value is set and printed
}
#IBAction func present(_ sender: Any) {
let newVc = self.storyboard?.instantiateViewController(withIdentifier: "stack") as! StackProgramViewController //instance 2 while presenting //your case newVc
newVc.items = itemsSelected //set value here with second instance nstaead of creating methodView
present(newVc, animated: true, completion: nil)
}
hope it helps
Why not use an initializer method on your class, and then pass it explicitly to the present method like so:
MethodViewController
class MethodViewController: UIViewController {
var items: [[String: Any]]?
init(withItems items:[[String: Any]]?) {
self.items = items
super.init(nibName: "MethodViewController", bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
print(items);
}
}
Previous View Controller
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let items = [["Stack":"Overflow"]]
let methodView = MethodViewController(withItems: items)
self.present(methodView, animated: true, completion: nil)
}
}
I have tested it and it will print out the items passed, also note the require init() method which the compiler forces you to have. You will also need to specify the bundle and nibName depending on how you set up your views.
I should note this approach is if you aren't trying to load from the storyboard, but rather from a separate .xib file with the same name.

Passing values from a custom view to Main View Controller - Swift

I am working on a basic alarm app. This is how the main storyboard looks like
I have added a custom view controller as a separate xib file which looks like this
And this is how the interface looks like when it runs. (The main ViewController in background and the CustomAlertController in the foreground)
It contains a date picker. What I mean to do is that when a user clicks on the addButton in the Main story board the customAlertViewController will come up and the user can choose a date and time to add as an alarm. When the user taps on Add in the customAlertViewController the date and time are supposed to be passed back into an array and added to the tableView in the Main storyboard view controller.
This is the code I have written so far:
Code for TableView
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let adate = alarmDate[indexPath.row].date
print(adate)
cell.textLabel?.text = String(adate)
return cell
}
Code in the Alarm class
import Foundation
class Alarm{
var date : NSDate
init (date : NSDate){
self.date = date
}
}
Code in CustomAlertViewController
You don't have to go through the entire code. I have tried using prepare for segue, but I guess that's not a doable solution when the CustomAlertviewcontroller is in a different storyboard(?)
The next approach I took was to somehow pass the date to an instance of Alarm class in the viewDidDisappear method, and subsequently append it to alarmDate array (declared in ViewController).
This is where I am getting stuck. The print statement in the viewDidDisappear outputs 1 to the console, obviously because the date has been appended. But once the CustomAlertViewController exits and the viewController is back, the alarmDate array resets and no value shows up in the table view. I have tried to work around this but to no avail.
I realise that if I had used a new view controller in the storyboard in place of a separate xib file, I could have achieved the same result easily.
class CustomAlertViewController: UIViewController {
//MARK: - Properties
#IBOutlet weak var customAlertView: UIView!
#IBOutlet weak var alarmPicker: UIDatePicker!
var destinationDate = NSDate()
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName : nibNameOrNil, bundle : nibBundleOrNil)
self.modalPresentationStyle = .OverCurrentContext
}
convenience init(){
self.init(nibName : "CustomAlertViewController", bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
//MARK: - Methods
override func viewDidLoad() {
super.viewDidLoad()
print ("View loaded")
self.customAlertView.layer.borderColor = UIColor.darkGrayColor().CGColor
self.customAlertView.layer.borderWidth = 2
self.customAlertView.layer.cornerRadius = 8
view.backgroundColor = UIColor(white: 1, alpha: 0.7)
view.opaque = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidDisappear(animated: Bool) {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("table") as! ViewController
let alarm = Alarm( date : destinationDate)
vc.alarmDate.append(alarm)
// vc.alarmData.reloadData()
print(vc.alarmDate.count)
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as! ViewController
let alarm = Alarm( date : destinationDate)
destinationVC.alarmDate.append(alarm)
destinationVC.alarmData.reloadData()
print(destinationVC.alarmDate.count)
}
//MARK: - Actions
#IBAction func cancelButton(sender: AnyObject) {
self.presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func addButton(sender: AnyObject) {
//Code to correct the time zone difference
let sourceDate = alarmPicker.date
let sourceTmeZone = NSTimeZone(abbreviation: "GMT")
let destinationTimeZone = NSTimeZone.systemTimeZone()
let sourceOffset = sourceTmeZone!.secondsFromGMTForDate(sourceDate)
let destinationOffset = destinationTimeZone.secondsFromGMTForDate(sourceDate)
let interval : Double
interval = Double(destinationOffset - sourceOffset)
destinationDate = NSDate.init(timeInterval: interval, sinceDate: sourceDate)
self.presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
}
}
I lack experience with Swift and would gladly appreciate any help.
PS I'm using Swift 2.3
You could use a protocol:
protocol DateShare {
func share(date: NSDate)
}
which you declare wherever you want outside of any controller scope.
then you add a
delegate: DateShare!
property in your CustomAlertVC. In the VC, when you init the CustomAlert, you add this line:
customAlert.delegate = self
And of course you add an extension that you conform to the DateShare protocol in your VC, as so:
extension ViewController: DateShare {
func share(date: NSDate) {
// do whatever you can for that the date can be displayed in table view
alarmDates.append(date) // if I understood it all ?
}
}
And finally you add this line in addButton(sender: _) scope:
#IBOutlet func addButton(sender: UIButton?) {
// generate inputDate here
delegate.share(date: inputDate)
}
This should make the trick.

How to create a base view controller

I would like to have a BaseViewController that subclasses UIViewController, overrides one of it's methods, but also require it's subclasses to implement new ones.
My case is a bit more complex, but this simple case represents my problem:
My BaseViewController would override viewWillAppear to set it's title, but the title string would come from it's subclasses. I thought about some options, not sure if/which one of them is best.
1 - Class with error throwing methods (my current solution):
class BaseViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
title = getTitle()
}
func getTitle() -> String {
fatalError("Unimplemented method")
}
}
2 - Receive the title in constructor:
class BaseViewController: UIViewController {
var myTitle: String!
convenience init(title titleSent: String) {
self.init(nibName: nil, bundle: nil)
myTitle = sentTitle
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
title = myTitle
}
}
Note that this options gets really bad if there's more parameters to send
I thought that using protocol would be perfect, until I find out that (of course) protocols can't subclass a class.
Didn't anybody do anything like this before? I don't think so, please share your thoughts.
Update
I tried another way, but got stuck in a compiler error, would this ever work?
procotol BaseViewController {
var myTitle: String { get }
}
extension BaseViewController where Self: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
title = myTitle
}
}
The compiler says Method does not override any method from its superclass.
I usually create protocol in which I declare what would be nice to have in the controllers. Then I check in the base controller if it's actually implemented and if so, just use the values, like this:
protocol ControllerType {
var navigationTitle: String { get }
}
extension ControllerType {
var navigationTitle: String {
return "Default title"
}
}
class BaseViewController: UIViewController {
override func viewDidLoad() {
if let controller = self as? ControllerType {
self.title = controller.navigationTitle
}
}
}
class ViewController: BaseViewController, ControllerType {
var navigationTitle: String {
return "ViewController"
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Downfall is you have to implement the ControllerType protocol and there's no way to enforce it.
Something similar would work.
class BaseViewController: UIViewController {
override func viewDidLoad() { }
func setTitle(_ title: String) {
self.title = title
}
}
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.setTitle("ViewController")
}
}

Swift Delegate not being called to close a UIViewController

I have a CenterViewController which contains a Game Controller. I want to add/remove a RulesViewController that the user can easily refer to as they play.
The RulesViewController appears and is dismissed fine. But the delegate.continueGame method is never called. I've added the protocol to RulesViewController. I've added a class extension to CenterViewController to handle the delegate. What am I missing?? Any help much appreciated...
Class CenterViewController: UIViewController {
private var controller: GameController
required init(coder aDecoder: NSCoder){
controller = GameController()
}
override func viewDidLoad() {
// add all the views here
let gameView = UIView(frame: CGRectMake(0,0, ScreenWidth, ScreenHeight))
self.view.addSubview(gameView)
controller.gameView = gameView
}
// method called when rules button on the gameView is pressed
func showRulesForLevel () {
let rulesViewController = storyboard!.instantiateViewControllerWithIdentifier("RulesViewController") as! RulesViewController
presentViewController(rulesViewController, animated: true, completion: nil)
// extension to the Class to handle the delegate
extension CenterViewController: RulesViewControllerDelegate {
//func to continue the game
func continueGame() {
controller.gameView.userInteractionEnabled = true
}
}
In the RulesViewController I have:
protocol RulesViewControllerDelegate {
func continueGame()
}
class RulesViewController: UIViewController {
var delegate: RulesViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// code to add a continue button which when pressed calls continueGameAction method
}
func continueGameAction() {
// dismiss the UIViewController so game can continue
self.dismissViewControllerAnimated(true, completion: nil)
// continue the game in CenterViewController
delegate?.continueGame()
}
}
BUT delegate?.continueGame() is never called.
Ok so you need to set the delegate in showRulesForLevel method like this:
rulesViewController.delegate = self
:)

Resources