Can a Delegate Apply to Two VCs - ios

I think I'm using protocols in a way that shouldn't work... but it does. Can someone tell me if what I'm doing is okay?
Example
Let's say my app has three view controllers: MainView, SecondaryView, EditorView. The EditorView has buttons that should run code in the other views. I have a protocol which allows me to trigger functions remotely.
Here's the weird thing, the thing I'm doing that maybe I shouldn't... but it's working: Both MainView and SecondaryView conform to the same protocol. I don't understand why it works, but it does.
When I am in MainView and I load the EditorView and, for example, click "Add", the code in the MainView runs and works.
When I am in SecondaryView and I load the EditorView and click "Add", the code in the SecondaryView runs and works.
Question
If this is okay, how is it figuring out which (Main vs Secondary) to delegate to? If it's not okay, why is it working?

This is not only okay - it's great. You've discovered one of the primary benefits of protocols.
In EditorView you have a delegate property (you might've named it something else):
weak var delegate : EditorProtocol?
You also have some code somewhere in MainView and SecondaryView that looks like:
let editor = EditorView() // or perhaps you get it in prepareForSegue(_:sender:)
editor.delegate = self
That tells EditorView which object to communicate with.
Then within EditorView you call code like:
delegate?.doSomething()
If MainView created this editor, then delegate is a MainView. Otherwise, it's a SecondaryView.
When you set delegate (or whatever you named it) to a specific object, that's what EditorView will communicate with.

Related

Add analytics event to log each ViewController's name in the project

I have a project in which I need to log an analytics event whenever any View Controller (log the name of the View Controller) comes on screen.
I was trying to avoid littering all of my existing View Controller classes with call to the analytics SDK.
I tried making an AnalyticsViewController and all my View Controllers would subclass this View Controller, and then I add analytics event in AnalyticsViewController class's viewDidLoad method. But the problem with this approach is that AnalyticsViewController does not which child View Controller is the call coming from.
I am using Swift 3.0. I believe that Swift with its powerful language features should be able provide me with an abstraction of some sorts.
Is there any way through this problem without littering all the View Controllers?
You were on the right track. Making a UIViewController parent class is a good idea.
In viewDidLoad method you can just add this:
let className = NSStringFromClass(self.classForCoder)
It will give you the name of current loaded view controller and then you can use that name in your event to specify which view controller was actually loaded.
Edit: added example.
So your parent's viewDidLoad would look something like this:
override func viewDidLoad() {
super.viewDidLoad()
let className = NSStringFromClass(self.classForCoder)
sendEvent(withViewControllerName: className)
}
The answer given by #JPetric is an amazing starting point. I just had to do a little modification to get it to work.
I've put this in my AnalyticsViewController to retrieve the name of the current subclass.
private func currentClassName() -> String? {
return NSStringFromClass(self.classForCoder).components(separatedBy: ".").last
}

Calling functions from separate view controllers in swift

I think the solution to this is going to need to use delegation, but I'm unfamiliar with how to use them.
So in my project, I have my main viewcontroller/storyboard that contains a UIScrollView. That UIScrollview calls another storyboard (xib file) as a subview. The other storyboard (which is an xib file) is controlled with another swift file.
My question is, when I call an action inside of my other storyboard, how can I call a function from the main viewcontroller. Like say the viewdidload from the first viewcontroller.
I can't make the whole thing a global function, it needs to stay inside its class. So if I try to do ViewController.viewDidLoad() it needs (I think) an instance variable or something.
Thanks.
You can try:
Using weak variable (property) in the other class with type UIViewController
Assign the parent view controller to that property after the other view is initialized
Good reads about weak, strong, unowned references Here And Here
Firstly, if you want to call it with class name as you said above declare your method with "class". So its just like static in Java. It makes it generic to call it anywhere in your project. Make a separate extension.
class func myfunc(){
}
if you want to send data from B to A controller. You use what is called delegation. You give the work of B to A. Make a protocol above B for functions that you want to do or send with them. Call them in B. And then in A write code for those functions. So that you have the data from B to A
Else you demand something like common data. Create a singleton class and initialize properties methods there. You can use objects for that and call it in other controller to modify or make different instances.
You dont call viewDidLoad(). As the name says it loads once. If you want something that modify everytime you screen appears, use viewWillAppear

How to set up #IBAction to target a UIView subclass

I'm trying a storyboard-free approach, and attempted the following:
I created a FooView.xib to hold my layout. It has a few buttons and labels.
I created a FooView.swift to hold a FooView subclass of UIView. At the moment, it doesn't really do anything.
I created FooViewController.swift to manage my FooView instances. I call it to create FooViews as follows:
init() {
super.init(nibName: "FooView", bundle: nil)
}
I set the File Owner of FooView.xib to the FooView class
I set the custom class of the FooView view as FooView
I dragged some #IBAction outputs from the IB to the FooView implementation
When my actions fire, I get a runtime exception: "unrecognized selector sent to instance" that references the FooViewController(!). I double checked the File Owner and all the outputs, all references look correct.
It acts like it ignores the File Owner with respect to the event dispatch.
(It feels like I'm fighting the framework and really am expected to implement all my outputs/actions inside the controller, but from an architectural purity perspective (to enhance testing) I was leaning towards hiding the xib control details and making a more opaque view that didn't directly expose its inner controls to the controller, but instead had a protocol/delegate kind of contract.)
I have a sneaky suspicion that if I moved all the xib-loading logic into the UIView implementation that things would work, but that feels like a larger adventure. Is there an easy way to set up my FooView subclass to receive the events?
Another thing you could use is set the First Responder as the target for your action. Although you won't get the nice neat indicator in XCode that you IBAction is connected, it will pass the message to the first class in the responder chain that implements that method.
To set it up in interface builder, click on First Responder and open the Attributes inspector. Add your method to the User Defined list as someAction: (don't forget the colon). Then you can connect your button to the first responder and that method will be an available option for you to connect it.
Lastly you declare your IBAction just like normal in the view class you want to implement it.

when to use viewDidLoad

I am new to both iOS development and programming in general. I need some clarification as to what sort of things should be declared in the viewDidLoad function of a UIViewController subclass
Thanks
In order to properly understand what viewDidLoad does, you should understand the View Controller Lifecycle. The best point to start is reading the Apple Documentation, e.g. the learning guides for developing iOS Apps: https://developer.apple.com/library/prerelease/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson4.html
Declare elements that don't need to be refreshed or recreated when the view reloads. For instance, viewDidLoad is called only when it is created while viewDidAppear will be called every time the view is shown.
Read up on some apple docs.
Everything you write inside the viewDidLoad function will run the the View(which can be TableView, ViewController & more..) is loaded.
For example, if you got a label called 'label' and you want to set it's by the code so you type:
override func viewDidLoad() {
super.viewDidLoad()
label.text = String("any text here")
}
and then the text of the label will change when the View will load.

Clean way to force view to load subviews early

Recently I wrote some code where I tried to refer to an outlet on a UIViewController I'd just instantiated with [storyboard instantiateViewControllerWithIdentifier] and modify the subview that the outlet pointed to before presenting the ViewController. It didn't work because the ViewController's view hadn't loaded its subviews yet, including the one that my outlet referred to, so the property just gave me a null pointer.
After (with some struggle) tracking down the cause of my issue in the debugger, I Googled around and learned, through answers like this one, that I can cause the view to load its subviews without being displayed by calling the myViewController.view getter. After that, I can access my outlet without any problems.
It's a clear hack, though, and Xcode - quite rightly - doesn't like it, and angrily protests with this warning:
Property access result unused - getters should not be used for side effects
Is there a non-hacky alternative way to do this that doesn't involved abusing the .view getter? Alternatively, are there canonical/idiomatic patterns for this scenario involving something like dynamically adding a handler to be called as soon as the subviews are loaded?
Or is the standard solution just to replace myViewController.view with [myViewController view] to shut up Xcode's warning, and then live with the hack?
On iOS 9 or newer, one can use:
viewController.loadViewIfNeeded()
Docs: https://developer.apple.com/reference/uikit/uiviewcontroller/1621446-loadviewifneeded
I agree that forcing a view to load should be avoided but I ran into a case where it seemed the only reasonable solution to a problem (popping a UINavigationController containing a UISearchController that had yet to be invoked causes a nasty console says warning).
What I did was use new iOS9 API loadViewIfNeeded and for pre-iOS9 used viewController.view.alpha = 1.0. Of course a good comment above this code will prevent you (or someone else) removing this code later thinking it is unneeded.
The fact that Apple is now providing this API signals it can be needed from time to time.
Not sure how much cleaner this way, but it still works fine:
_ = vc.view
UPD: for your convenience, you can declare extension like below:
extension UIViewController {
func preloadView() {
let _ = view
}
}
You can read explaination by following URL: https://www.natashatherobot.com/ios-testing-view-controllers-swift/
merged Rudolph/Swany answers for pre ios9 deployment targets
if #available(iOS 9.0, *) {
loadViewIfNeeded()
}
else {
// _ = self.view works but some Swift compiler genius could optimize what seems like a noop out
// hence this perversion from this recipe http://stackoverflow.com/questions/17279604/clean-way-to-force-view-to-load-subviews-early
view.alpha = 1
}
If I understand you correctly, I think there's another fairly standard solution: move the outlet modification/configuration code into a viewDidLoad method (of the recently instantiated VC).
The topic is also discussed in this question.
It would require some restructuring, but it might give you a "cleaner" design in terms of MVC if your incoming VC handled its own configuration, and it would avoid the "You should never call this method directly" stricture on loadView.
You can call [myViewController loadView] to explicitly load the view, instead of abusing the .view getter. The .view getter actually calls loadView if necessary when called.
It's still not a very nice solution, since the UIView Documentation's section on loadView explicitly instructs that
You should never call this method directly

Resources