I'm new in iOS development,
I have and "API Helper" Swift class that gets some data as a JSON array. And when the array is ready I want to call a method in my MasterViewController to update the tableView with the data.
I tried to do like that:
var facilities : [Facility]? {
didSet {
MasterViewController().facilitiesLoaded()
}
}
And then reload the tableView but without seeing anything.
I think the problem is that I'm creating a new instance of the ViewController, but what I should have is to get access to the current instance of the class.
Any idea, or a better design? Thanks..
If this "facilities" variable is instance of MasterViewController then do :
var facilities : [Facility]? {
didSet {
self.facilitiesLoaded()
}
}
Thanks to #dcestari,
I did added callback blocks to the API call and handled it in the ViewController and that did the trick
In API caller method:
func loadFacilities(completionHandler:(() -> Void!)) {
// do stuff
completionHander()
}
In the ViewController:
func getFacilities() {
api.loadFacilities({
// update tableView
})
}
Related
I have a protocol declared in a class
public protocol demoDelegate {
func willShowdemoResult(DemoGraph: UIView)
}
Now I am calling this in the same class where the protocol is declared.
public class Demo:UIViewController {
public var delegate : demoDelegate!
//some code
self.delegate.willShowdemoResult(self.demoGraph())
}
where demo graph returns a UI graph
func demoGraph() -> UIView {
//some code
return demoGraphView
}
I am getting an error that unexpectedly found nil while wrapping an optional value. I know the reason that I have not initialised the delegate. Can somebody guide me How to initialise the delegate here.
The function is being called in other class
class DemoResult: UIViewController, demoDelegate{
func willShowdemoResult(DemoGraph: UIView)
// some code
}
Please Help
You are getting the error, because Demo.delegate is nil when calling:
delegate.willShowdemoResult(self.demoGraph())
Before you make this call, make sure, that you have set the delegate property. I would recommend this right after initializing Demo or right after DemoResult got the address of the Demo-instance.
Let's assume, you have stored an instance of Demo in DemoResult.demoVC. Then you can set the delegate in DemoResult like this:
demoVC.delegate = self
BTW: It's better to use optional types to store delegates:
public var delegate: demoDelegate?
When delegate is optional, delegate?.willShowdemoResult(self.demoGraph()) won't crash, if delegate has not been initialized yet.
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.
TDLR; I have three classes, when Class A object is updated, it's calling on its delegate (Class B) which is calling on its delegate (Class C) without doing anything else. Class C will use Class B in different ways depending on the values in Class A. Class B need to know of its Class A at touch events. Is this acceptable?
classA { var information: String }
classB { var thing: ClassA thing.delegate = self }
classC { var things: [ClassB] for thing in things { thing.delegate = self } }
My real example
I have three classes: A mapViewController, a mapMarker and a place (model). The map holds multiple mapMarkers, and every mapMarker has a property place, which contains information of what the marker should look like (like place type, "bar", "restaurant" etc). The place might receive new information via a silent push notification, and hence being updated. When the place is updated, I need to notify the mapViewController that the marker needs to be redrawn (I'm using MapBox and their annotations doesn't support redrawing in any way but removing and adding the marker again, since the imageForAnnotation method is a delegate one.)
My first thought was to make two protocols placeDelegate and mapMarkerDelegate.
Place:
protocol PlaceDelegate: class
{
func placeUpdated()
}
class Place {
weak var delegate: PlaceDelegate?
var propertyThatCanBeUpdate: String {
didSet {
//Checking if the newValue == oldValue
delegate.placeUpdated()
}
}
MapMarker
protocol MapMarkerDelegate: class
{
markerShouldReDraw(mapMarker: MapMarker)
}
class MapMarker: PlaceDelegate {
var place: Place!
weak var delegate: MapMarkerDelegate?
init(place: Place) {
self.place = place
place.delegate = place
}
func placeUpdate()
{
delegate.markerShouldReDraw(self)
}
}
MapViewController
class MapViewController {
//I could easily set the marker.delegate = self when adding the markers
func markerShouldReDraw(mapMarker: MapMarker)
functionForRedrawingMarker()
}
This feels a bit ugly, and a bit weird that the MapMarker is just passing the "my place has been updated" information forward. Is this acceptable as far as performance goes? Should I use some kind of NSNotification instead? Should I make the MapViewController the delegate of place and search my array of mapMarker for the one holding the correct place?
I have a feeling there is more than one problem with this code, but my first issue is that my delegate returns nil and I do not know why. First, is my delegate:
import UIKit
//delegate to move information to next screen
protocol userEnteredDataDelegate {
func userDidEnterInformation(info:NSArray)
}
Next, I have a var defined for the delegate and I believe the ? makes it an optional variable? This is defined inside the class
var dataPassDelegate:userEnteredDataDelegate? = nil
Now, after my user has entered information into the fields in the view, I want to add those field values to an array and then pass that array on to the next view where it will be added to. I have pieced this code together from some YouTube examples but I think I am missing a needed part. When do I assign some kind of value to the dataPassDelegate var so it is not nil when the if statement comes? Do I even need that if statement?
if blankData != 1 {
//add code to pass data to next veiw controller
enteredDataArray = [enterDate.text, enterSeason.text, enterSport.text, enterDispTo.text]
//println(enteredDataArray)
self.appIsWorking ()
if (dataPassDelegate != nil) {
let information: NSArray = enteredDataArray
println(information)
dataPassDelegate!.userDidEnterInformation(information)
self.navigationController?.popViewControllerAnimated(true)
} else {
println ("dataPassDelegate = nil")
}
//performSegueWithIdentifier("goToDispenseScreenTwo", sender: self)
activityIndicator.stopAnimating()
UIApplication.sharedApplication().endIgnoringInteractionEvents()
}
blankData = 0
}
Your help is appreciated.
A delegate is a pointer to another object that conforms to a particular protocol. Often you use delegates to refine the behavior of your class, or to send back status information o the results of an async network request
When you set your dataPassDelegate delegate is up to you.
What is the object that has the dataPassDelegate property? What object will be serving as the delegate?
You need to create 2 objects (the object that will be serving as the delegate, and the object that has the dataPassDelegate property) and link them up.
We can't tell you when to do that because we don't know what you're trying to do or where these objects will be used.
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")
}
}