How do I initialise view controller with custom data? - ios

I'm using present to open a view controller.
self.storyboard = UIStoryboard(name: "myStoryboard", bundle: nil)
self.myView = self.storyboard?.instantiateInitialViewController() as? myViewController
self.present(self.myView!, animated: true) { _ in }
Then in the view controller I'm trying to get data from initialiser
class myViewController: UIViewController {
var dataA: String?
var dataB: String?
override init(dataA: String, dataB: String) {
self.dataA = dataA
self.dataB = dataB
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I get an error Initializer does not override a designated initializer from its superclass
How should I override init and pass data in this case?

I do not think there is a way, other than not using storyboards. Even Swinject does that in a separate method.
class myViewController: UIViewController {
var dataA: String?
var dataB: String?
fun initialize(dataA: String, dataB: String) {
self.dataA = dataA
self.dataB = dataB
}
}
and in the other class
self.storyboard = UIStoryboard(name: "myStoryboard", bundle: nil)
self.myView = self.storyboard?.instantiateInitialViewController() as? myViewController
self.myView.initialize(dataA: dataA, dataB: dataB)
self.present(self.myView!, animated: true) { _ in }
or you can do it in other way and create a static func, that will return an instance. But I prefer the first way.
class myViewController: UIViewController {
var dataA: String?
var dataB: String?
static func instance(dataA: String, dataB: String) -> myViewController {
//You could pass storyboard too
let storyboard = UIStoryboard(name: "myStoryboard", bundle: nil)
let myView = storyboard.instantiateInitialViewController() as? myViewController
self.dataB = dataB
self.dataA = dataA
return myView
}
}

Related

Clean Swift dataStore and routing

I am develop am app in Clean architecture, I want pass data to main screen after user logged in, here is my router file:
import UIKit
protocol LoginRoutingLogic: class {
func routeToRegisterController()
func routeToRecoveryPassword()
func routeToMainPage()
}
protocol LoginDataPassing: class {
var dataStore: LoginDataStore? { get }
}
final class LoginRouter: LoginRoutingLogic, LoginDataPassing {
weak var viewController: LoginController?
var dataStore: LoginDataStore?
func routeToRegisterController() {
let storyboard = UIStoryboard(name: Constants.Identifiers.registerControllerIdentifier, bundle: nil)
if let viewcontroller = storyboard.instantiateViewController(withIdentifier: Constants.Identifiers.registerControllerIdentifier) as? RegisterController {
viewController?.navigationController?.pushViewController(viewcontroller, animated: true)
}
}
func routeToRecoveryPassword() {
let storyboard = UIStoryboard(name: Constants.Identifiers.forgotPasswordControllerIdentifier, bundle: nil)
if let viewcontroller = storyboard.instantiateViewController(withIdentifier: Constants.Identifiers.forgotPasswordControllerIdentifier) as? RecoveryPasswordController {
viewController?.present(viewcontroller, animated: true, completion: nil)
}
}
func routeToMainPage() {
let storyboard = UIStoryboard(name: "MainPageController", bundle: nil)
if let viewcontroller = storyboard.instantiateViewController(withIdentifier: "MainPageController") as? MainPageController {
viewController?.navigationController?.pushViewController(viewcontroller, animated: true)
}
}
}
my user model, that I want to pass the values to the main page, making the call works just fine:
import Foundation
struct User: Codable {
var token: String?
var name: String?
var email: String?
var password: String?
var statusCode: Int?
}
struct LoginError: Codable {
}
extension User {
static func parse(responseData: Data?) -> User? {
var user: User?
guard let data = responseData else {
return user
}
do {
let decoder = JSONDecoder()
user = try decoder.decode(User.self, from: data)
} catch let err {
print("Error: ", err)
}
return user
}
}
and the viewController that I want to pass the username to the username label:
import UIKit
protocol MainPageDisplayLogic: class {
func getData(viewModel: LoginModel.Fetch.ViewModel)
}
final class MainPageController: UIViewController {
var interactor: MainPageBusinessLogic?
var router: (MainPageRoutingLogic & MainPageDataPassing)?
var builder = MainPageBuilder()
// MARK: Object lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setUpView()
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
// MARK: Setup
private func setup() {
let viewController = self
let interactor = MainPageInteractor()
let presenter = MainPagePresenter()
let worker = MainPageWorker()
let router = MainPageRouter()
viewController.interactor = interactor
viewController.router = router
interactor.presenter = presenter
interactor.worker = worker
presenter.viewController = viewController
router.viewController = viewController
router.dataStore = interactor
}
}
extension MainPageController: MainPageDisplayLogic {
func getData(viewModel: LoginModel.Fetch.ViewModel) {
let name = viewModel.name
builder.usernameLabel.text = name
}
}
extension MainPageController: ViewCodeProtocol {
func setUpView() {
viewHierarchy()
makeConstraits()
setupViewNavigationBar()
}
func viewHierarchy() {
view.addSubview(builder.usernameLabel)
}
func makeConstraits() {
builder.usernameLabelConstraits()
}
func setupViewNavigationBar() {
navigationController?.setNavigationBarHidden(false, animated: true)
}
}
And the router from the main page, I could no properly figure that datastore thing yet, how do a pass the data between controllers in clean swift? at least without userdefaults.
import Foundation
protocol MainPageRoutingLogic: class {
}
protocol MainPageDataPassing: class {
var dataStore: MainPageDataStore? { get }
}
final class MainPageRouter: MainPageRoutingLogic, MainPageDataPassing {
weak var viewController: MainPageController?
var dataStore: MainPageDataStore?
}
I have figured that out, I just simply have to use the dataStore references in the destination ViewController and retrieve the data.

Pass value from child view page to Pageview controller in swift

I am android developer and pretty new to iOS.
I have a pageView Controller say A. It is connected to three child View Controller. I want to get back a value from child View Controller back to Root Page View Controller.
In android it can be easily done through interface passing and having trouble in doing in iOS with protocols.
I am doing like this
protocol Z {
func runZ()
}
Class A: UIViewController, Z {
func runZ( withValue value: String)
{
//Perform some function
}
var orderedViewControllers: [UIViewController] = {
var myViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "myViewController") as! MyViewController
myViewController.z = self
return [ myViewController,
UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "C")]
}()
}
Class B : UIViewController{
var z = A.self
func sendBackToA(){
(z as! Z).runZ(withValue : "CustomString")
}
I get the error at myViewController.z = self as
Cannot assign value of type '(A) -> () -> A' to type 'A.Type'.
I made it work somehow by initializing like this
myViewController.z = A.self
but my app is crashing
Could not cast value of type 'A.Type' (0x1e0d854d8) to 'Z'
protocol Z:class {
func runZ(withValue value: String?)
}
class A: UIViewController, Z, UINavigationControllerDelegate {
func runZ(withValue value: String?)
{
//Perform some function
}
var orderedViewControllers: [UIViewController] = {
var myViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "B") as! B
myViewController.z = self
return [ myViewController,
UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "C")]
}()
}
class B : UIViewController{
weak var z:Z?
func sendBackToA(){
z?.runZ(withValue : "CustomString")
}
}
A few things....
protocol Z {
func runZ(withValue value: String)
}
class A: UIViewController, Z { // "class" rather than "Class"
func runZ( withValue value: String)
{
//Perform some function
}
func orderedViewControllers() -> [UIViewController] { // func because variable declarations don't have a "self"
// Should get rid of the forced unwrap in the next line
let myViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "myViewController") as! B
myViewController.z = self
return [ myViewController, UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "C")]
}
}
class B : UIViewController { // "class" rather than "Class"
var z: Z? // The other controller will do the initialization.
func sendBackToA(){
// Verify that the delegate is registered
if let z = z {
z.runZ(withValue : "CustomString")
} else {
print("Missing delegate assignment")
}
}
}

Swift generic function to push any view controller

I am attempting to write a func that 1) instantiates a subclass of UIViewController and 2) pushes into the navigation controller of the caller UIViewController.
So far, I have this:
func pushAnyViewController<T>(viewController:T, storyboardName:String) {
// Instantiate the view controller of type T
guard let nextViewController = UIStoryboard(name: storyboardName, bundle: nil).instantiateViewController(withIdentifier: String(describing: T.self)) as? T else {
return
}
viewController.navigationController.pushViewController(nextViewController, animated: true)
}
This produces error
Value of type 'T' has no member 'navigationController'
I am not sure if somehow I should say that T will always be a subclass of UIViewController. If that is the case, it's not clear where I do that. For this, I thought about:
func pushAnyViewController<T>(viewController:T & UIViewController, storyboardName:String)
but that produces errors:
Generic parameter 'T' is not used in function signature
Non-protocol, non-class type 'T' cannot be used within a protocol-constrained type
You need to identify that T is a vc with <T:UIViewController>
func pushAnyViewController<T:UIViewController>(viewController:T, storyboardName:String) {
guard let nextViewController = UIStoryboard(name: storyboardName, bundle: nil).instantiateViewController(withIdentifier: String(describing: T.self)) as? T else { return }
viewController.navigationController?.pushViewController(nextViewController, animated: true)
}
The top answer didn't suit me, I needed a more generic solution. First, I needed the option to pass data, second, I wanted to have a generic function that can push or present VC, and last, I wanted that my generic function can be called from anywhere, not just from the UIViewController, that's why an extension of UIViewController didn't suit me.
I decided to create a struct with simple init, and two public methods so that I can create the copy anywhere and call those methods.
struct Navigator {
// MARK: - DisplayVCType enum
enum DisplayVCType {
case push
case present
}
// MARK: - Properties
private var viewController: UIViewController
static private let mainSBName = "Main"
// MARK: - Init
init(vc: UIViewController) {
self.viewController = vc
}
// MARK: - Public Methods
public func instantiateVC<T>(withDestinationViewControllerType vcType: T.Type,
andStoryboardName sbName: String = mainSBName) -> T? where T: UIViewController {
let storyBoard: UIStoryboard = UIStoryboard(name: sbName, bundle: nil)
let destinationVC = storyBoard.instantiateViewController(withIdentifier: String(describing: vcType.self))
return destinationVC as? T
}
public func goTo(viewController destinationVC: UIViewController,
withDisplayVCType type: DisplayVCType = .present,
andModalPresentationStyle style: UIModalPresentationStyle = .popover) {
switch type {
case .push:
viewController.navigationController?.pushViewController(destinationVC, animated: true)
case .present:
destinationVC.modalPresentationStyle = style
viewController.present(destinationVC, animated: true, completion: nil)
}
}
}
and example call in some VC, with passing string after push:
class SomeVC: UIViewController {
var navigator: Navigator?
override func viewDidLoad() {
super.viewDidLoad()
navigator = Navigator(vc: self)
}
func pushVC() {
guard let vc = navigator?.instantiateVC(withDestinationViewControllerType: VC1.self) else { return }
vc.someString = "SOME STRING TO BE PASSED"
navigator?.goTo(viewController: vc, withDisplayVCType: .push)
}
func presentVC() {
guard let vc = navigator?.instantiateVC(withDestinationViewControllerType: TableViewController.self) else { return }
navigator?.goTo(viewController: vc, withDisplayVCType: .present)
}
}
With Help of some keyword.
enum Storyboard : String {
case Main
}
func viewController(_ viewController: UIViewController.Type) -> some UIViewController {
return UIStoryboard(name: self.rawValue, bundle: nil).instantiateViewController(withIdentifier: String(describing: viewController.self))
}

nil Delegate between two ViewController with two different Bundle (swift)

nil Delegate between two ViewController with two different Bundle using swift 4 (commented in second code)
here is my code :
First ViewController :
class FirstVC : UIViewController, MerchantResultObserver{
var secVC = SecondVC()
override func viewDidLoad() {
secVC.delegate = self
let storyboard = UIStoryboard(name: “SecondVC”, bundle: Bundle(identifier: “SecondBundle”))
let controller = storyboard.instantiateInitialViewController()
self.present(controller!, animated: true, completion: nil)
secVC.initSecondVC(data)
}
func Error(data: String) {
print("-------------Error Returned------------- \(data)")
}
func Response(data: String) {
print("-------------Response Returned------------- \(data)")
}
}
Second ViewController :
public class SecondVC: UIViewController {
public weak var delegate: MerchantResultObserver!
public func initSecondVC(_ data : String){
print(data)
}
#IBAction func sendRequest(_ sender: UIButton) {
delegate?.Response(data: “dataReturnedSuccessfully”) // delegate is nil //
dismiss(animated: true, completion: nil) // returned to FirstVC without returning “dataReturnedSuccessfully” //
}
}
public protocol MerchantResultObserver: class{
func Response(data : String)
func Error(data : String)
}
Any help would be appreciated
var secVC = SecondVC()
and
let storyboard = UIStoryboard(name: “SecondVC”, bundle: Bundle(identifier: “SecondBundle”))
let controller = storyboard.instantiateInitialViewController() as? SecondVC
These both are different instances.
You can assign a delegate to the controller, like
controller.delegate = self
It will call the implemented delegate methods in First View Controller.
Full Code.
let storyboard = UIStoryboard(name: “SecondVC”, bundle: Bundle(identifier: “SecondBundle”))
if let controller = storyboard.instantiateInitialViewController() as? SecondVC {
//Assign Delegate
controller.delegate = self
//It's not init, but an assignment only, as per your code.
controller.initSecondVC(data)
self.present(controller, animated: true, completion: nil)
}
One more thing, Don't present View in ViewDidLoad. You can put a code in some button or in a delay method.

How to encapsulate an UIViewController (like UIAlertController) in Swift?

I have a ViewController in my Storyboard which works like an alert (with a title, a message, and two buttons).
I would like to encapsulate it to be able to use it anywhere in my code, like this :
let alert = CustomAlertViewController(title: "Test", message: "message de test.", view: self.view, delegate: self)
self.present(alert, animated: false, completion: nil)
My problem is that the IBOutlets are not initialised...
My CustomAlertViewController :
public protocol CustomAlertProtocol {
func alertAccepted()
}
class CustomAlertViewController: UIViewController {
var delegate :CustomAlertProtocol? = nil
var parentView :UIView?
var blurScreenshot :SABlurImageView?
var alertTitle :String? = nil
var alertMessage :String? = nil
#IBOutlet weak var oAlertView: UIView!
#IBOutlet weak var oAlertTitle: UILabel!
#IBOutlet weak var oAlertMessage: UILabel!
//MARK: - Main
public convenience init(title: String?, message: String?, view: UIView, delegate: CustomAlertProtocol) {
self.init()
self.alertTitle = title
self.alertMessage = message
self.delegate = delegate
self.parentView = view
}
override func viewDidLoad() {
oAlertTitle.text = self.alertTitle
oAlertMessage.text = self.alertMessage
}
#IBAction func onAcceptButtonPressed(_ sender: AnyObject) {
delegate?.alertAccepted()
}
}
Set the Custom Class property of your View Controller to CustomAlertViewController
and Storyboard ID to whatever you want - e.g. CustomAlertViewControllerIdentifier in the Identity Inspector of the InterfaceBuilder.
And then instantiate it like following:
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
guard let vc = storyboard.instantiateViewControllerWithIdentifier("CustomAlertViewControllerIdentifier") as? CustomAlertViewController else {
return
}
edit:
You can then put that code in a class function like:
extension CustomAlertViewController {
class func instantiateFromStoryboard(title: String?, message: String?, view: UIView, delegate: CustomAlertProtocol) -> CustomAlertViewController {
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let vc = storyboard.instantiateViewControllerWithIdentifier("CustomAlertViewControllerIdentifier") as! CustomAlertViewController
vc.title = title
vc.message = message
vc.view = view
vc.delegate = delegate
return vc
}
}
and then use like:
let myCustomAlertViewController = CustomAlertViewController.instantiateFromStoryboard(title: "bla", ...)

Resources