How to get references for ViewController - ios

Community!
I have a question regarding References in Swift 3 and Xcode.
My Plan is this:
At the moment my App has 4 Controllers. A MapViewController, a PositionController, a MarkerController and a APIDataController. Those of course handle the respective Models. My problem now comes in, when I want the controllers to communicate. Look at this code (Please ignore that most functions aren't implemented here):
First my MarkerController:
import Foundation
protocol MarkerControllerDelegate: class{
func willGetMarkerArray()
func didGetMarkerArray(_ newMarkerArray: Marker)
}
class MarkerController: NSObject{
var markerArray = [Marker]()
weak var delegate: MarkerControllerDelegate?
func createMarkerArray(){
}
func getMarkerArray() -> [Marker]{
return markerArray
}
}
extension MarkerController : APIDataControllerDelegate{
func didRetriveAPIData(_ APIDataModelArray: APIDataModel) {
createMarkerArray()
}
}
And now my APIDataController:
import Foundation
protocol APIDataControllerDelegate: class{
func didRetriveAPIData(_ APIDataModelArray: [APIDataModel])
}
class APIDataController: NSObject {
var APIDataModelArray = [APIDataModel]()
weak var delegate: APIDataControllerDelegate?
func retriveAPIData(){
//get the data
//.....
//finished getting data
delegate?.didRetriveAPIData(APIDataModelArray)
}
func getAPIDataModelArray() -> [APIDataModel] {
return APIDataModelArray
}
}
And finally the beginning of my MapViewController:
import UIKit
import GoogleMaps
import GoogleMaps
import CoreLocation
class MapViewController: UIViewController{
let positionController = PositionController()
let apiDataController = APIDataController()
let markerController = MarkerController()
override func viewDidLoad() {
super.viewDidLoad()
apiDataController.retriveAPIData()
//blablabla
}
//blablablabla
}
Now, this might be a lot of code at once, so let me explain you what is ought to happen:
At the beginning (or wherever in the code) MapViewController calls the retrieveAPIData method. Once the function is finished, APIDataController is planned to notify the MarkerController via the APIDataControllerDelegate-Protocol to start his work (run his createMarkerArray-Method).
Now, to do this, I have to declare a markerController-Instance as a Delegate to APIDataController. Now, since I have already created instances of all my controllers in mapViewController, I want to give those references to my other controllers where needed so that they all refer to the same Instances, kinda like a Singleton.
But, how do I create a Reference to my MapViewController IN my MapViewController, to pass it to my other Controllers so that they can from there go like:
let positionController = mapViewController.positionController?
Nothing I tried seem to work. So any help would be appreciated.
Also if there are better ways to let my Controllers communicate I am open to here them, I am sure there is a better way to do it than I currently try to do.

Using Delegates for the communication between the controllers is the recommended way to do it. Setting the delegates depends on the way these controllers interact in your app. Often you will have a segue from one app to another, that is a good chance to set delegates. You can also do it in code that is putting together your views

Related

How to handle not used functions from delegates in view controllers

I have a very general view that is created and used by multiple view controllers with 2 buttons, one of them sometimes is hidden depending on the needs.
This view delegates the tap of the two buttons.
protocol TheViewsDelegate: class {
func button1Tapped()
func button2Tapped()
}
Let's put that ViewControllerA creates this view and needs both buttons, this view controller will have to implement both delegate functions and do something inside it.
Now let's say that ViewControllerB creates the same view but just needs one of the buttons. This view controller will have to still implement button2Tapped() even though it will never be called and used.
Is there a way to handle this nicely? I imagine there's a nice solution where I don't need to implement this button2Tapped() if I don't need it.
I thought about making it optional by giving a default implementation but I don't like this solution, I like (and I think it's a good practice) the compiler giving me an error when a method it's not implement. Someone can jump into the project and not realising that he/she hasn't implement button2Tapped when needs to be implemented.
Note: This is a very simple example just to illustrate my question, but the question is more broad as in what to do when a function in a delegate is defined by controller that don't need to implement it.
I believe you want to use:
optional func
There are a couple of ways of declaring a protocol method as optional, one is using optional func which requires using #objc syntax, which a lot of programmers apparently don't like, and the other requires declaring an empty body in the extension of a protocol (which makes it optional by default).
protocol TheViewsDelegate: AnyObject {
func button1Tapped()
}
extension TheViewsDelegate {
func button2Tapped() {}
}
class SomeViewController: UIViewController, TheViewsDelegate {
func button1Tapped() {
// implement
}
}
By giving the protocol an empty body inside an extension of the protocol, that method is optional and does not need to be implemented by conforming objects.
For comparison, the alternative:
#objc protocol TheViewsDelegate: AnyObject {
func button1Tapped()
#objc optional func button2Tapped()
}
class SomeViewController: UIViewController, TheViewsDelegate {
func button1Tapped() {
// implement
}
}

How to call the same function on multiple classes?

I have an UIViewController with 4 UIButtons. A user can tap any of those UIButtons and an UIView pops up. I want to add an didAppear() and didDisappear() function on the classes which are holding the UIViews depending on the users action. How can I call didDisappear() without the use of an enum, for example:
func didDisappear(view: EnumViews){
switch view{
case view0: myClassWithView0.didDisappear()
case view1: myClassWithView1.didDisappear()
case view2: myClassWithView2.didDisappear()
case view3: myClassWithView3.didDisappear()
}
}
Now I get 4 times duplicate data. I know that function exists for my class with a UIView, but how to call it? I made a protocol:
protocol ViewProtocol{
func didAppear()
func didDisappear()
}
I made the classes which are holding the UIView's conform to that protocol. However I do not know how to use it, when I create the class I get the error:
'myClassWithUIView' cannot be constructed because it has no accessible
initializers
The classes are all in an array and I can identify which UIView needs to pop up from the sender.tag. Ideally, I want to have something like this:
#IBAction func bringNewUIView(_ sender: UIButton) {
let newView = myArrayOfClassesWithUIView[sender.tag]
newView.didAppear()
}
You've got many things going on here. I'll start with the easy one.
'myClassWithUIView' cannot be constructed because it has no accessible initializers
This just means you don't have an initializer for your class. So inside your myClassWithUIView implementation you need to have init. I can't really help you with building the init because I don't know how that class is structured, but I will assume this is something you know how to do anyway.
Your #IBAction seems fine. Once you have an array of your classes that seems like it should work. Edit your post if that is not the case.
Finally, for your didDisappear question, you can do something like this:
func didDisappear(view: EnumViews) {
//Check to see if this view conforms to your ViewProtocol (that's not a good name, btw)
if let myClass = view as? ViewProtocol {
//Since it does conform to ViewProtocol you can call didDisappear on it
myClass.didDisappear()
}
}
Alternatively, if you already know that the didDisappear function is always passing in a view that conforms to ViewProtocol why not just change the argument and make that easier?
func didDisappear(view: ViewProtocol) {
view.didDisappear()
}

class instance to all ViewControllers

I wish to avoid writing let APIHelper = API() in every UIViewController, instead I did this:
extension UIViewController {
func APIHelper() -> API {
let api = API()
return api
}
}
and now it is working like self.APIHelper().callMethod(), but I'm not really sure if it is the way to do it. Any tips on best practice?
Your extension useless, since it just same as calling API() everytime:
self.APIHelper().callMethod()
self.APIHelper().callSecondMethod() //here you created another API instance
same as
API().callMethod()
API().callSecondMethod()
If API is singletone, idea looks ok, but in swift you usually create singletone with static constant:
class API {
static let sharedAPI = API()
//...
}
and access to it like this:
API.sharedAPI.callMethod()
API.sharedAPI.callSecondMethod() //now called in same API instance
If you don't want to write API.sharedAPI everytime, then you can use:
Your extension
extension UIViewController {
var apiHelper: API {return API.sharedAPI}
}
Not recommended as #NickCatib explained.
Base view controller
as #NickCatib suggested (easier with variable):
class BaseViewController: UIViewController {
// some of the code you might need
let apiHelper = API.sharedAPI
}
Protocol
If you use API in view controllers time to time, can be better declare protocol
protocol APIHelper {
var apiHelper: API {get}
}
with default implementation
extension APIHelper {
var apiHelper: API {return API.sharedAPI}
}
and connect it to your viewController only when needed
class ViewControllerThatNeedsAPI: UIViewController, APIHelper {
// apiHelper avalible here
}
With all three ways you access your API like this:
apiHelper.callMethod()
How about some kind of base view controller where you can extend with that declaration? This way ALL of your view controllers have that function, even when you don't need it.
This would go like
class BaseViewController: UIViewController {
// some of the code you might need
func APIHelper() -> API {
let api = API()
return api
}
}
And later:
class ViewControllerThatNeedsAPI : BaseViewController {
// You have it here
}
Another approach that I actually use is to have service/manager for API calls that handles that, and send all the data needed via delegate/NSNotification/completion handler. That way your code will be cleaner and easier to test ( if you practice tests ). If you keep everything in the view controller you will break the SRP. This managers are PONSO - Plain old ns objects. You could use the same way as for view controllers and have some BaseService with API URL, basic stuff that needs to be overriden etc. After that you just implement service and call it when needed - depending on the implementation have some function to reflect data to UI.

Swift Property that conforms to a Protocol and Class

#property (strong, nonatomic) UIViewController<UITableViewDelegate> *thing;
I want to implement a property like in this Objective-C code in Swift. So here is what I've tried:
class AClass<T: UIViewController where T: UITableViewDelegate>: UIViewController {
var thing: T!
}
This compiles. My problem comes when I add properties from the storyboard. The #IBOutlet tag generates an compiler error.
class AClass<T: UIViewController where T: UITableViewDelegate>: UIViewController {
#IBOutlet weak var anotherThing: UILabel! // error
var thing: T!
}
The error:
Variable in a generic class cannot be represented in Objective-C
Am I implementing this right? What can I do to fix or get around this error?
EDIT:
Swift 4 finally has a solution for this problem. See my updated answer.
Update for Swift 4
Swift 4 has added support for representing a type as a class that conforms to a protocol. The syntax is Class & Protocol. Here is some example code using this concept from "What's New in Swift" (session 402 from WWDC 2017):
protocol Shakeable {
func shake()
}
extension UIButton: Shakeable { /* ... */ }
extension UISlider: Shakeable { /* ... */ }
// Example function to generically shake some control elements
func shakeEm(controls: [UIControl & Shakeable]) {
for control in controls where control.isEnabled {
control.shake()
}
}
As of Swift 3, this method causes problems because you can't pass in the correct types. If you try to pass in [UIControl], it doesn't have the shake method. If you try to pass in [UIButton], then the code compiles, but you can't pass in any UISliders. If you pass in [Shakeable], then you can't check control.state, because Shakeable doesn't have that. Swift 4 finally addressed the topic.
Old Answer
I am getting around this problem for the time being with the following code:
// This class is used to replace the UIViewController<UITableViewDelegate>
// declaration in Objective-C
class ConformingClass: UIViewController, UITableViewDelegate {}
class AClass: UIViewController {
#IBOutlet weak var anotherThing: UILabel!
var thing: ConformingClass!
}
This seems hackish to me. If any of the delegate methods were required, then I would have to implement those methods in ConformingClass (which I do NOT want to do) and override them in a subclass.
I have posted this answer in case anyone else comes across this problem and my solution helps them, but I am not happy with the solution. If anyone posts a better solution, I will accept their answer.
It's not the ideal solution, but you can use a generic function instead of a generic class, like this:
class AClass: UIViewController {
#IBOutlet weak var anotherThing: UILabel!
private var thing: UIViewController?
func setThing<T: UIViewController where T: UITableViewDelegate>(delegate: T) {
thing = delegate
}
}
I came across the same issue, and also tried the generic approach. Eventually the generic approach broke the entire design.
After re-thinking about this issue, I found that a protocol which cannot be used to fully specify a type (in other words, must come with additional type information such as a class type) is unlikely to be a complete one. Moreover, although the Objc style of declaring ClassType<ProtocolType> comes handy, it disregards the benefit of abstraction provided by protocol because such protocol does not really raise the abstraction level. Further, if such declaration appears at multiple places, it has to be duplicated. Even worse, if multiple declarations of such type are interrelated (possibly a single object will be passed around them ), the programme becomes fragile and hard to maintain because later if the declaration at one place needs to be changed, all the related declarations have to be changed as well.
Solution
If the use case of a property involves both a protocol (say ProtocolX) and some aspects of a class (say ClassX), the following approach could be taken into account:
Declare an additional protocol that inherits from ProtocolX with the added method/property requirements which ClassX automatically satisfy. Like the example below, a method and a property are the additional requirements, both of which UIViewController automatically satisfy.
protocol CustomTableViewDelegate: UITableViewDelegate {
var navigationController: UINavigationController? { get }
func performSegueWithIdentifier(identifier: String, sender: AnyObject?)
}
Declare an additional protocol that inherits from ProtocolX with an additional read-only property of the type ClassX. Not only does this approach allow the use of ClassX in its entirety, but also exhibits the flexibility of not requiring an implementation to subclass ClassX. For example:
protocol CustomTableViewDelegate: UITableViewDelegate {
var viewController: UIViewController { get }
}
// Implementation A
class CustomViewController: UIViewController, UITableViewDelegate {
var viewController: UIViewController { return self }
... // Other important implementation
}
// Implementation B
class CustomClass: UITableViewDelegate {
private var _aViewControllerRef: UIViewController // Could come from anywhere e.g. initializer
var viewController: UIViewController { return _aViewControllerRef }
... // UITableViewDelegate methods implementation
}
PS. The snippet above are for demonstration only, mixing UIViewController and UITableViewDelegate together is not recommended.
Edit for Swift 2+: Thanks for #Shaps's comment, the following could be added to save having to implement the desired property everywhere.
extension CustomTableViewDelegate where Self: UIViewController {
var viewController: UIViewController { return self }
}
you can declare a delegate in Swift like this:
weak var delegate : UITableViewDelegate?
It will work with even hybrid(Objective-c and swift) project. Delegate should be optional & weak because its availability is not guaranteed and weak does not create retain cycle.
You are getting that error because there are no generics in Objective-C and it will not allow you to add #IBOutlet property.
Edit: 1. Forcing a type on delegate
To force that delegate is always a UIViewController you can implement the custom setter and throw exception when its not a UIViewController.
weak var _delegate : UITableViewDelegate? //stored property
var delegate : UITableViewDelegate? {
set {
if newValue! is UIViewController {
_delegate = newValue
} else {
NSException(name: "Inavlid delegate type", reason: "Delegate must be a UIViewController", userInfo: nil).raise()
}
}
get {
return _delegate
}
}

Assigning Functions from other Classes in Swift

Is it possible to access and run a specific method/function from another class that can change dynamically as the app is run?
I’ll try to simplify the problem as much as possible.
SelectionPage.swift
Choose which class needs to be selected and accessed using an UIPickerView - 10 possible selections (Class1, Class2, Class3,…, Class10).
Class1.swift, Class2.swift, … Class10.swift
Each of the 10 classes has a single method that has exactly the same name but is programmed differently:
func runOnUpdate() { }
GameSceneViewController.swift
When a selection is made on the SelectionPage, the app segues to a GameSceneViewController where the specific selected function is run every time the update function is run:
override func update(currentTime: CFTimeInterval)
{
// run runOnUpdate() function here from selected class
}
Inside the update function, I would like to execute the runOnUpdate( ) function depending on which class was selected on the SelectionPage. Is this possible? Ideally I'd like to be able to assign the specific class/method in the:
override func didMoveToView(view: SKView)
so that I can access in other functions as well.
I’ve looked into lazy instantiation, creating delegates for each of the classes, #objc(Class1), arrays of [AnyClass], typealias, global variables in structs, singletons etc. but I’m unable to figure out how to make this work.
It seems like a fairly common problem so any help would be greatly appreciated! Thank you in advance!
You were correct in trying delegates as this is a case where you should make a protocol and a delegate. The protocol requires the function. From there you set the delegate property to an instance of a class that conforms to that protocol and then you call delegate?.someFunction() to call the function on the given object.
class ViewController: UIViewController {
var delegate: Updatable?
override func viewDidLoad() {
super.viewDidLoad()
let foo = Foo()
delegate = foo
delegate?.runOnUpdate() // prints do something
}
}
protocol Updatable {
func runOnUpdate()
}
class Foo: NSObject, Updatable {
func runOnUpdate() {
println("do something")
}
}

Resources