Automatically update an UILabel at var update in swift - ios

At some point of my project I got an UILabel displaying the number of element in an array.
I want to update automatically the label text when I modifying the array.
I can make my UILabel global and access it with a "didSet" on the array, but I don't want to make all my UILabel global (I got 8 UILabel for 8 different evolutive var).
Here is a way to update the UILabel content like a pointer or a reference on a specific variable ?
Edit
Some code example :
I got a global which is a dictionary,
let eg:[string : [SomeClass]]!
In a ViewController I got a UILabel, with
label.text = eg["key"].count
I want to automatically update the value display if I do something like that
eg["key"].append(something)

Since you are using global data, one approach you might try is using NSNotificationCenter. If you were to encapsulate your global data inside an object, the object could post a notification every time the data is updated.
This approach will allow multiple observers the opportunity to act on changes to global state "automatically". It will also add the benefit of keeping your UIKit elements from being exposed.
Inside the userInfo property of the posted notification, you would place the key and associated count value.
Make your view controller an observer of this notification. When it receives the notification, it can update its UILabels itself, using the data received inside userInfo.
A simple implementation might look like this:
class SomeClass {
// ...
}
class MyGlobalObject {
var eg:[String : [SomeClass]]!
static let sharedInstance = MyGlobalObject()
private init() {
eg = ["someKey":[], "someOtherKey":[]]
}
func appendTo(key:String, value:SomeClass) {
eg[key]?.append(value)
NSNotificationCenter.defaultCenter().postNotification(NSNotification(name:"ValueChanged", object: nil, userInfo: ["Key":key, "Count" : (eg[key]?.count)!]))
}
}
class ViewController: UIViewController {
// Define labels, etc....
override func viewDidLoad() {
super.viewDidLoad()
// Put this wherever makes the most sense. viewWillAppear() works too. Don't forget to remove yourself as an observer when you are done.
NSNotificationCenter.defaultCenter().addObserver(self, selector:"didUpdate:", name: "ValueChanged", object: nil)
}
func didUpdate(notif:NSNotification) {
print("Received Notification!")
let userInfo = notif.userInfo
// Update appropriate label with the data
}
}
Then anywhere in your app, you could do this:
MyGlobalObject.sharedInstance.appendTo("someKey", value: SomeClass())

Related

How would I change the value of a UILabel from a different class in swift?

I am a student trying to make an app in Xcode, and I have run into an issue to which I cannot figure out the solution. I want to access a string I have stored in another class and make it the label text of my ViewController.
In my ViewController, I call a different class, which then calls through an API and finds me a string detailing the weather in a certain place. This is what my call looks like from my ViewController class
func getWeather() {
manager = DataManager()
manager.fetchData(query: getQueryString();
}
And this is what the call looks like from the DataManager Class
func rainyWeatherPopulate(str: String){
LoadEvents.loadEvents(type: EventType.rain, str: str)
}
in the loadEvents function, I get a string detailing the type of weather that I want to put as text of a UILabel in my ViewController class, and I am not sure how to do so
Is there any way that I can pass an instance of my ViewController to my loadEvents static function or any other way in which I can set the text of a UILabel in my ViewController from the LoadEvents class?
There are many potential ways to solve this problem, but one is by passing a closure to be used upon completion of the call.
So, it might look like this in your use case:
class DataManager {
func fetchData(query: String, onComplete: #escaping (String) -> Void) {
LoadEvents.loadEvents(type: .rain, str: query, onComplete: onComplete)
}
}
enum EventType {
case rain
}
class LoadEvents {
static func loadEvents(type: EventType, str: String, onComplete: (String) -> Void) {
//load the data
//then, when it's done, call the completion
onComplete("returnedData")
}
}
func getWeather() {
let manager = DataManager()
let uiLabel = UILabel()
manager.fetchData(query: "queryString", onComplete: { result in
uiLabel.text = result
})
}
I had to mock out some stuff (like EventType) and I had no information about the types of events you're loading, so I just used a generic String for the return type, but the concept is here.
How it works:
In getWeather, there's a closure called onComplete that will get data back once everything has completed. result holds (obviously) the result -- in this case a string, and it sets uiLabel's text property to that value.
LoadEvents does whatever it needs to and then upon finishing, calls onComplete and sends the results back through the closure.
I'd say jnpdx's solution of passing a closure to your data manager is the best answer.
Another way you could do this is to add a delegate property to your data manager. The only thing you know about the delegate is that it conforms to a specific protocol. You'd then invoke methods on the delegate from the data manager that would tell the delegate about data that was available for display. The delegate would decide what to do with that data.
I mention this as an alternative method, but would suggest using closures as jnpdx recommends in their answer.

Is there a way to have a custom class act on a UIView without having the ViewController passed (as reference) upon its initialization?

Example: I have a SpeechSynthesizer class that needs to update something in my UIView when it’s done uttering a piece of text. Since the SpeechSynthesizer class conforms to protocol AVSpeechSynthesizerDelegate, it is the one that receives the didFinish signal when the uttering has been completed. The idea here is to keep the ViewController from having too many delegate methods and a long list of protocols to conform to. The workaround I found was to have the ViewController passed in as a SpeechSynthesizer initialization parameter. This way I get to access the ViewController connected to the UIView I want to update from inside the SpeechSynthesizer class. The thing I don’t like about it is that it looks kind of ugly to have the ViewController passed in as a parameter to every single class that needs to use it. So I wonder, which other way I could accomplish this.
I suppose another way to ask the question is: How can I make the function
private func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance)
return something to a ViewController since it's not "called" by it?
I added a reply on Quora. Copying it here:
After doing some research and testing on code of my own here are 2 solutions to this problem.
Solution 1: The Delegate Pattern:
Create a custom delegate protocol in the ViewController
protocol ViewControllerDelegate:class {
func getViewLayer() -> CALayer
}
The ViewController must conform to this newly created protocol and therefore implement all the functions defined by it, so somewhere in the class ViewController you add:
public func getViewLayer() -> CALayer {
return self.view.layer
}
Then on my custom class, ReadTextMachine, I added a variable of the ViewControllerDelegate type
private weak var viewControllerDelegate: ViewControllerDelegate?
The variable must be weak and protocol must be of type class in order to solve a “retain cycle” problem (since both the custom class and the ViewController will point to each other)
You’ll notice now that the function call inside the ViewController is already “callable” from the custom class, so in my ReadTextMachine I added:
let viewLayer = self.viewControllerDelegate?.getViewLayer()
self.cameraPreview = CameraPreview(session: self.camera.getSession(), container: viewLayer!)
self.cameraPreview?.addPreview()
In the above case, my CameraPreview (yes, a 3rd class in this example) simply adds a camera preview layer on the UIView. For that it needed access to the main View’s layer.
The above code still doesn’t work because our original viewController’s instance hasn’t been passed as reference anywhere in our code. For that we add the following function in ReadTextMachine:
public func setViewControllerDelegate(viewController: ViewController) { // call this from the ViewController so that ViewController can be accessed from here.
self.viewControllerDelegate = viewController
}
The above piece of code will have to be called from the ViewController, after we instantiate our custom class (ReadTextMachine), so that the viewControllerDelegate inside it points to the ViewController. So in our ViewController.swift:
operatingMode = ReadTextMachine()
operatingMode.setViewControllerDelegate(viewController: self)
Another example and explanation can be found in this video from LetsBuildThatApp. I derived my solution mostly from it.
My current app in development applying the above solution can be found here: agu3rra/World-Aloud
Solution 2: Notifications and Observers pattern
This one is a bit easier to understand and follow. The general idea is to have your custom class broadcast a message which triggers a function call on your ViewController since it has an observer setup, waiting to hear that message.
So to give an example, in the context I used it, I have a CameraCapture class which uses AVFoundation to capture a photo. The capture photo trigger cannot immediately return an image, since iOS has a set of steps to execute before actually generating an image. I wanted my ReadTextMachine to resume an activity after CameraCapture had a photo available. (To apply this in the context of the CustomClass triggers ViewController event is basically the same, since both are actual classes in an iOS app as well).
So the 1st thing I did was create a broadcast function since I would use it in many places in my app. I simply placed it in a Utilities.swift file in the Xcode project.
public func broadcastNotification(name: String) {
let notification = Notification.Name(rawValue: name)
NotificationCenter.default.post(name: notification, object: nil)
}
The above function takes a string, which must be a unique notification identifier, and broadcasts it thru NotificationCenter.
In my CameraCapture class, I added a static constant to reference the unique identifier of the message:
static let NOTIFY_PHOTO_CAPTURED = "agu3rra.worldAloud.photo.captured"
For those who know AVFoundation, a photo is available when event didFinishProcessingPhoto gets executed, so at the end of that I added:
broadcastNotification(name: CameraCapture.NOTIFY_PHOTO_CAPTURED)
The above is a call to my previously defined utility function.
For my ReadTextMachine class to be able to catch that notification, I added the following on its init() and deinit routines:
override init() {
super.init()
// Setup event observers
let notification1 = Notification.Name(rawValue: CameraCapture.NOTIFY_PHOTO_CAPTURED)
NotificationCenter.default.addObserver(self,
selector: #selector(self.processingDoneTakingPhoto),
name: notification1,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self) // cleanup observer once instance no longer exists
}
Removing the observer is important at deinit so that when your object is deallocated from memory, the observer isn’t left lingering around. The above configured observer triggers a function call inside ReadTextMachine:
#IBAction private func processingDoneTakingPhoto() {
// does my stuff
}
That’s it! Again, the entire Xcode project can be downloaded from my project’s Git repository: agu3rra/World-Aloud
Hope this can be of use to others.
Cheers!

NotificationCenter addObserver(observer:selector:name:object) -- what is object?

I am having trouble understanding what the object parameter is in NotificationCenter.default.addObserver(observer:selector:name:object)
If I understand it correctly, it acts as a kind of filter; only notifications posted from this object will be observed. But I can't seem to actually figure out how to use it.
I created a class and made a global instance of it
class FooClass {
func postNotification() {
NotificationCenter.default.post(name: NSNotification.Name("TestNotification"), object: self)
}
}
let globalFoo = FooClass()
Then in my first ViewController I press a button which calls globalFoo.postNotification()
Then in my second ViewController I registered like so:
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(notificationReceived), name: NSNotification.Name("TestNotification"), object: globalFoo)
}
func notificationReceived() {
print("notification received")
}
}
It works fine when I don't specify object (i.e. nil), so clearly I'm misunderstanding what it is.
The object parameter used when posting a notification is to indicate what object is actually posting the notification.
When adding an observer, you can leave object nil and you will get all of the named notifications regardless of which object actually sent the notification. Or you can specify a specific object when adding an observer and you will then only be notified when that specific object posts the named notification.
Some notifications use this parameter to provide more appropriate information to the observer.
For example, notifications like NSManagedObjectContextObjectsDidChange optionally accepts NSManagedObjectContext object so that it can notify changes only from that context.

iOS Delegate is returning nil (Swift)

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.

Delegate Error in Swift

I am trying to send a double value from a UIView (which is loaded from a XIB) to a ViewController using a delegate
Here is my protocol, it is just sending a double to the main ViewController on a button click.
protocol HoursWorkedDelegate{
func sendHoursWorked(hoursWorked: Double);
}
class HoursWorkedView: UIView{
var delegate: HoursWorkedDelegate?;
#IBAction func calculateHoursWorked(sender: AnyObject){
// Do some calculations for the hoursWorked value
// Give value
delegate!.sendHoursWorked(hoursWorked);
}
}
// This class now wants that Double value
class ViewController: UIViewController, HoursWorkedDelegate{
// Conform to protocol
func sendHoursWorked(hoursWorked: Double){
// Lets say we just want to assign this value to a textField
hoursWorkedTextField.text = NSString(format: "%.4f", hoursWorked);
}
}
The error message I get is Thread 1: EXC_BAD_INSTRUCTION(code = EXC_I386_INVOP, subcode=0x0)
Any help would be much appreciated, Thank You!
As a start, change the exclamation point in this snippet to a question mark:
delegate!.sendHoursWorked(hoursWorked);
This is what's likely causing the crash, as you are force-unwrapping the optional delegate property. A question mark means we'll only call sendHoursWorked() on the delegate if the delegate exists.
That fix will now probably mean that your program is no longer crashing, but you still don't get the desired results, because sendHoursWorked() is never called. We have to tell our HoursWorkedView object who is delegating it.
Somewhere in your code, you might have something like this:
let hoursWorkedView = HoursWorkedView()
self.view.addSubview(hoursWorkedView)
It's right here where we should be setting the delegate:
let hoursWorkedView = HoursWorkedView()
hoursWorkedView.delegate = self
self.view.addSubview(hoursWorkedView)
Though if it's me, I probably add a constructor to HoursWorkedView that accepts the delegate property:
init(delegate: HoursWorkedDelegate) {
super.init()
self.delegate = delegate
}
And now we can just do this:
let hoursWorkedView = HoursWorkedView(delegate: self)
self.view.addSubview(hoursWorkedView)
I think you're getting your view and your viewcontroller mixed up: a ViewController controls things; a view just displays them. The viewController tells the view what to display.
So, you want to connect your button to the viewController -- not the view. And you don't need a custom view class or a delegate.
Set it up like this:
create a textField and a button
create an outlet for the textField
put calculateHoursWorked directly in your viewController
create an action to connect the button to calculateHoursWorked
in calculateHoursWorked, set self.textField.text to the result of the calculation (where "textField" is whatever you named your outlet)
You wouldn't use a delegate in this context because the viewController knows everything the view does. The delegate pattern is for cases where one object has no visibility into another.
EDIT:
That being said, the bug here is that the delegate isn't actually being set anywhere.
Swift Optionals (the ! and ?) help prevent cases like this. If you explicitly unwrap an optional using !, you have to make sure it's always defined. In this case, since delegate is defined as optional (?) you have to check it:
#IBAction func calculateHoursWorked(sender: AnyObject){
// Do some calculations for the hoursWorked value
// Give value
if let currentDelegate = self.delegate {
currentDelegate.sendHoursWorked(hoursWorked)
}
}

Resources