I am creating an ios app with swift. I have an UIViewController and an "UserManager" class which inherits from NSObject. Basically, when the user opens the app, the code within UIViewController is executed. UIViewController calls a function in UserManager. When this function has finished, I want that a segue be performed (a segue called "connectSegue" in my code). I am a beginner...
What I have done (and didn't work!) :
Here is my ViewController :
import UIKit
var profileUser = UserManager()
class ViewController : UIViewController{
override func viewDidLoad() {login()}
func login()->Void {profileUser.createUser()}
func handleSegue()->Void {self.performSegueWithIdentifier("connectSegue",sender: self)}
}
Here is my UserManager class :
import Foundation
class UserManager: NSObject {
//Some properties
func createUser()->Void {
//Some code
var segueHandler = ViewController()
segueHandler.handleSegue()
}
}
How can I do that?
Thank you
The problem is that your UserManager is creating a new instance of ViewController, which is not the one that called UserManager.
A better way to handle this is to call performSegueWithIdentifier in your ViewController, after calling login.
In general, you are better off not putting UI navigation inside non-UI classes. UserManager will be more useful if it does user functions only, not UI responsibilities. If UserManager is doing its work asynchronously, then you should consider using notifications to communicate back to your ViewController when the work is complete so it can perform the segue.
Related
I'm new to Swift and I'm sure this question is pretty basic and has been asked and answered before.
I am not using storyBoard. My main viewController is created from AppDelegate via code.
I have:
a custom class defined in a model.swift file
a main viewController (from AppDelegate) that I am using as a container
3 additional viewcontrollers as subviews of the main (not each other)
all 3 subviews are displayed simultaneously each covering 1/3 of the screen (no segues)
each viewcontroller is in a separate .swift file
I want to create an instance of my custom class in the main viewController and have all 3 of the subviews be able to reference that instance.
Each of the subview view controllers need to be able to get/set instance variables and the other subviews need to be made aware of those changes.
I think I will need to use notifications to communicate the changes to the multiple subviews - but I haven't even begun to try and figure that out yet.
If this has been asked and answered before - could someone please either provide a link - or provide me with the right search terms so that I'm able to find the answer? The only found answers I've found that come close are to use segues to pass the data back and forth.
You can use delegate pattern. Below code is assuming that you are using MVVM pattern. (It is very similar for VIPER/ReSwift patterns also)
protocol DataChangedDelegate {
func refreshData()
}
// ViewModel for FirstViewController
class FirstViewModel {
var delegate: DataChangedDelegate?
var data: Any {
didSet {
delegate?.refreshData()
}
}
//rest of the things
}
//similarly other two view models will have a delegate and on data change will call the refresh method
And your view controllers should adopt this protocol
class FirstViewController: UIViewController, DataChangedDelegate {
//view controller code
//delegate code
func refreshDate() {
//tableView.reloadDate()
//collectionView.reloadDate()
//lableView.text = viewModel.data()
}
}
And where ever you create a viewControllers and add as subView, you have to set the delegate of viewModel.
let firstViewController: FirstViewController = createFirstViewController()
let firstViewModel = FirstViewModel()
firstViewModel.delegate = firstViewController
firstViewController.viewModel = firstViewModel
mainViewController.addSubView(firstViewController.view)
Similarly for all other view controllers.
Here's how I would do it:
Create a singleton class.
Configure the singleton's properties in the the main ViewController.
Use didSet to post a Notification.
Add a listener for that Notification in your additional ViewControllers.
Is it somehow possible to have a protocol with a default implementation via protocol extension to get some code automatically executed by just implementing the protocol in various places?
Heres an example of what I mean:
protocol Greetable {
func greet()
}
extension Greetable where Self: UIViewController {
func greet() {
print("Hello ViewController")
}
func viewDidLoadMagicFunctionHookIdontKnowIfItIsPossible() {
greet()
}
}
class MyViewController: UIViewController, Greetable {}
So I want to define a protocol, give it default implementations and then hook the extension to let's say viewDidLoad() or other functions without explicitly calling the greet function by myself in every instance.
Subclassing is not possible since I want to use UITableViewController, UIViewController, UINavigationController, UISplitViewController, etc. and I cannot create one generic baseclass for all of them at once.
I also don't want to go through all of my view controllers and call greet() in viewDidLoad() because what if I decide to move the greet() to viewDidAppear? I had to go through all the controllers and move the code.
By the way, the order of when this magic function would be called would not matter at all for me, in case I have two protocols with this magic.
I'm surely not the only one who thought about something like this and there are programmers a thousand times smarter than me who surely can exactly explain why and how or why it is not possible in Swift to "hook" yourself into a certain function call.
I have 3 classes :
Core that doesn't inherit from any class.
vcMain and vcIncomingFile which both inherit from UIViewController.
I have a segue from vcMain to vcIncomingFile.
How can I call the -performSegueWithIdentifier: method from the Core class,between vcMain and vcIncoming. I mean I want to have a method or delegate or anything else in Core class that can performSegue from vcMain to vcIncomingFile.
class Core {
func showIncomingVC(){ }
}
in showIncomingVC function, I want to performSegue between vcMain and vcIncomingFile.
Thanks
To implement the patterns properly we would need to know if your Core class is part of your model or if it is some kind of control flow mechanism.
If it is part of the model, it should know nothing about the view controllers but the view controllers are allowed to access it. In this case your view controllers could implement a delegate protocol of some sort and establish a call path from your Core class by assigning themselves as a delegate for some part of the Core functionality. The Core could then trigger any pre-defined behaviour in its delegates by calling the methods defined in the protocol.
For example,
If you define a protocol called FileEventDelegate with a method named fileReceived().
Then, add a member to your Core class called fileEventDelegate of type FileEventDelegate?
Whenever the Core class receives a file, it can call fileEventDelegate?.fileReceived(). The object instance, of whichever class implements the protocol, that registered itself as the delegate will handle it from there.
A unit testing class could also be the delegate and not even have nor need a segue to be performed.
Your Core class could even work without a delegate being set.
On the UI side, your VcMain class can implement the FileEventDelegate protocol by defining a fileReceived() function that call performSegueWithIdentifier(...). On viewLoaded() it can set itself as the fileEventDelegate of the Core class instance it is working with.
This keeps all model-to-visual logic in the viewController where it belongs.
If your Core class works in complete separation of the view controllers (meaning that the view controllers don't know how to access the instance(s) of Core), you may want to look into NSNotificationCenter and send notifications out in the universe for your viewControllers to pick-up asynchronously.
Core needs to be of UIViewController or some class that inherit UIViewController because performSegueWithIdentifier is a function of UIViewController
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/performSegueWithIdentifier:sender:
You can work with protocol instead. Create a protocol in your Core class with func showIncomingVC(){ } and use its delegate in your view class
Just do like this:
struct Segue {
let sourceVC: UIViewController
let sender: AnyObject
let identifier: String
}
protocol SegueProtocol: class {
var segue: Segue { get }
}
class Core {
weak var delegate: SegueProtocol?
func perform() {
guard let segue = delegate?.segue else {
return
}
segue.sourceVC.performSegueWithIdentifier(segue.identifier, sender: segue.sender)
}
}
I'm, relatively, a beginner, so this may be an entirely common practice—or an entirely impossible one—but I've been wondering if it's possible to modify a view controller added in a storyboard so that instead of (or in addition to?) being an instance of UIViewConroller, it's also an instance of (blahblah)ViewController, e.g. ABUnknownPersonViewController.
That way, instead of doing something like this:
class ViewController : UIViewController {
override func viewDidLoad() {
let test = ABUnknownPersonViewController()
...
self.presentViewController(test, animated: false, completion: nil)
}
}
This could be done:
class ViewController : ABUnknownPersonViewController {
override func viewDidLoad() {
//ViewController already is an ABUnknownPersonViewController, so you can treat it as one
//example below (displayedPerson is a property of ABUnkownPersonViewControllers)
self.displayedPerson...
}
}
EDIT: ABUnknownPersonViewController is a class supplied by Apple, which does not support subclassing (here). With that said, and the understanding that I would obviously like as simple a solution as possible (avoidance of protocols and whatnot), what are my options?
I tried class FourthViewController: UIViewController, ABUnknownPersonViewController, ABUnknownPersonViewController, ABUnknownPersonViewControllerDelegate only to get an error about multiple inheritance.
It sounds like what you actually want to do is to subclass UIViewController, and in your storyboard, set the custom class to your subclass. When the view controller is loaded from the storyboard, it will be an instance of your subclass.
So your subclass would look like this:
class ABUnknownPersonViewController : UIViewController {
override func viewDidLoad() {
self.displayedPerson...
}
}
In the storyboard, highlight the view controller you want to use a custom class for, and in the Custom Class field, type the name of your subclass. If you've done it correctly, it should autocomplete for you.
I'm trying to pass data from the modal ViewController to his source ViewController. I think I have to use delegation but it doesn't work.
protocol communicationControllerCamera{
func backFromCamera()
}
class Camera: UIViewController{
var delegate: communicationControllerCamera
init(){
self.delegate.backFromCamera()
}
}
class SceneBuilder: UIViewController, communicationControllerCamera{
func backFromCamera(){ // Never called
println("YEAHH")
}
}
The backFromCamera method it's not called. What did I do wrong?
You didn't set a delegate so it was empty when you tried to call backFromCamera().
Here's a simple working example you can test out. Notice the use of the optional type (?) for the delegate.
// Camera class
protocol communicationControllerCamera {
func backFromCamera()
}
class Camera: UIViewController {
var delegate: communicationControllerCamera? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.delegate?.backFromCamera()
}
}
// SceneBuilder class
class SceneBuilder: UIViewController, communicationControllerCamera {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
var myCamera = Camera()
myCamera.delegate = self
self.presentModalViewController(myCamera, animated: true)
}
func backFromCamera() {
println("Back from camera")
}
}
You can find all the information you need in Apple's Swift documentation.
Obviously the chosen answer is correct, but it didn't help me. I did successfully implement protocols though, so I wanted to provide my own explanation in case anyone is struggling with grasping the concept, like I was.
Protocol Code Is Written in Three Places:
Two ViewController Classes
The Protocol itself (code written outside of VC classes)
When I write my protocols, I put them in my "ToolBox" document and I still write comments to remind myself which VCs are doing what. Two examples:
So there is always:
The protocol code (shown above)
Code in a VC which initiates the action
Code in a VC which is delegated to carry out the action
1. The protocol code
See the image above for a reference. Essentially, the protocol code is just where you give the protocol a name and declare what functions you want to remotely call/delegate to. Name the protocol. Declare the names of the functions that can be called upon and declare their parameter types such as string, etc.
2. Code in a VC which initiates the action
This is the code that initiates the protocol. In this example, this is code from a table cell, which needs to delegate some work back to the main table VC. The first screenshot shows the creation of the delegate variable and the second screenshot is the actual use of that variable.
So the below code are table-cell buttons. They all need to trigger code outside of the cell VC, so they all trigger functions using the protocol I declared above.
3. Code in a VC which is delegated to carry out the action
Now the protocol is being called, but which VC answers the call? To answer that question, choose the VC and add the protocol name to the class declaration:
Lastly, you need the actual meat of the whole thing. Not the trigger, not the protocol itself, not the class declaration... but the actual function you want to call:
Hope This Helps
I don't know why protocols just wouldn't sink through my thick skull but they wouldn't. I hope this helps others like me!