Can't Update Label from Other Class in Swift

Update label.text from another class

You can use Notifications and observers to control your code behavior between your data and your controllers.
On the end of your function that define the boolean variable that you check in the viewDidload of your viewController post a notification like this :

NotificationCenter.default.post(name: NSNotification.Name(rawValue: "VariableDefined"), object: nil)

With this you create a notification called VariableDefined (you can name it as you want). object parameter allows you to send data or whatever parameter you want with this notification.
Then, you have to set up a listener to listen to this notification. Who is interested on your boolean variable ? Your viewController.
Put a listener of this notification on your view controller like this :

let name = Notification.Name(rawValue: "VariableDefined")
NotificationCenter.default.addObserver(
self, selector: #selector(checkVariable),
name: name, object: nil)

With this you are defining a listener to the notification called VariableDefined and handle it with the method checkVariable.
Finally, on this handler you can update your label title :

func checkVariable() {
let defaults = UserDefaults.standard
if !defaults.bool(forKey: "XXXXXX.\
(GlobalVariables.sharedinstance.circuitselectionne!)"){
titre.text = "2,90€"
} else {
titre.text = "Démarrer la visite ! "
}
}

Hope it will help you. That's a way to communicate between models and view controllers.

How to change Labels and Table View from another Class?

I found solution. First of All the variable selectedLanguage I declared it out of the class SettingsViewController.

import UIKit

var selectedLanguage = 0 //<<< Here

class SettingsViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {

var languages = ["English", "German", "French"]

After that I did this in method viewDidLoad to every ViewController (I will show just one ViewController).

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.

if (selectedLanguage == 0) { //English
foodTitleLabel.text = "Food"
foodList = ["Apple", "Bread", "Pineapple", "Water", "Other"]
}
else if (selectedLanguage == 1) { //German
foodTitleLabel.text = "Lebensmittel"
foodList = [ "Apfel", "Brot", "Pineapple", "Wasser","Andere"]
}
if (selectedLanguage == 2) { //French
foodTitleLabel.text = "Aliments"
foodList = ["Pomme", "Pain", "Ananas", "Eau", "Autre"]
}

I hope this will be helpful for other users, because It worked perfectly for me.

Can't update Label from other class in Swift

Regarding the "unexpectedly found nil while unwrapping an Optional value" error, the problem is that you have an optional (judging from the code snippet you've provided, it must be an implicitly unwrapped optional) that is nil. Most likely, mylabel is nil. Either print the value or add a breakpoint and examine the property in the debugger to confirm.

If it is nil, you then have to figure out why. In our discussion, it turns out that you're trying to call the Log event in the view controller:

ViewController().Log("asdf")

The problem is that this doesn't call Log in the existing view controller, but rather the ViewController() expression ends up instantiating a new, completely unrelated view controller with its outlets not hooked up to anything. Thus the attempt to update the implicitly unwrapped outlet will cause the error you shared with us.

If you want this separate class (a database manager object) to inform the view controller of event in order to allow the view controller to update the UI, there are three common approaches:

  1. Completion/progress handlers that are closures/blocks.

    This is used for simple interface where the database needs to inform view controller when the request is done.

  2. Delegate-protocol pattern.

    The delegation pattern (usually conforming to some well-established protocol) is used for rich interfaces where the database needs to inform the view controller of a variety of different types of events.

  3. Notification pattern.

    Notifications are used when you want a loosely coupled interface between the database object and whatever is handling these notifications. The view controller might register itself as an observer of any notifications of a particular name with the defaultCenter() of the NSNotificationCenter. The database object can then post notifications of that name (supplying details via the userInfo dictionary) and the view controller will be informed of these events.

How to update a UILabel class from another class

You said:

The UILabel can be displayed in different view controllers, so I can't tell it to register a NSNotification observer on load because that means every time the label is loaded it registers the observer again.

No, this is precisely what you should do for capturing updates to some Int value that needs to be reflected in multiple view controllers. Sure, every time a view controller is loaded (e.g. viewDidLoad) you’d add the NotificationCenter observer, and then remove the observer in deinit. But adding/removing observers is not an appreciably expensive process, so just do it. It sounds like the logical solution for the problem you describe.

FWIW, this does not seem like a good delegate-protocol candidate if multiple view controllers need to be informed of changes to this object. The delegate-protocol pattern is best used when there is a one-to-one relationship between an object and its delegate. But when you have many different view controllers that would like to be the “delegate”, in turn, then an observer pattern makes a lot more sense.


By the way, you reference something that “constantly receives updates” in conjunction with it being a “user default”. If there’s anything in your question that I’d take a hard look at, it would be the notion of using user defaults to capture something that isn’t really a default, but rather something that is presumably a model object that is frequently changing.


For example, let’s assume that I had defined some custom notification:

extension Notification.Name {
static let didUpdateFoo = Notification.Name(Bundle.main.bundleIdentifier! + ".didUpdateFoo")
}

And when Foo is updated, you’d do:

NotificationCenter.default.post(name: .didUpdateFoo, object: foo)

Then a view controller that wants to observe this notification would:

private var didReceiveFooObserver: NSObjectProtocol?

override func viewDidLoad() {
super.viewDidLoad()

didReceiveFooObserver = NotificationCenter.default.addObserver(forName: .didUpdateFoo, object: nil, queue: .main) { [weak self] notification in
guard let foo = notification.object as? Int else { return }
self?.fooLabel.text = "\(foo)"
}
}

deinit {
if let token = didReceiveFooObserver {
NotificationCenter.default.removeObserver(token)
}
}

Updating label in other class with a function

I think here there is a little confusion between UIKit and SpriteKit.

A ViewController has no member addChild, I think you would speak about SKNode object.

In SpriteKit , if you want to display a SKNode element or an objects that inherit SKNode, first of all you should cast your view as a SKView then instantiate and present a SKScene as explained for example in the "Hello world" project :

class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
class GameScene: SKScene {
override func didMove(to view: SKView) {
print("This is my GameScene, here I could show my SKLabelNode")
}
}

Also, in SpriteKit you don't need to change viewController to display your elements, you can present a new SKScene or show directly a SKNode


To help you about your function you can do also:

func showMessage(message: String, color: UIColor,pos:CGPoint)->SKLabelNode{
let messageLabel = SKLabelNode(fontNamed: "Verdana")
messageLabel.text = message
messageLabel.fontSize = 15
messageLabel.fontColor = color
return messageLabel
}

Usage:

let messagePos = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
let messageLabel = showMessage(message:"Test Message", color: UIColor.green, pos:messagePos)
self.addChild(messageLabel)

Can't update label after getting location

Since didUpdateLocations & reverseGeocodeLocation methods are called asynchronously, this guard may return as of nil address

guard let myResult = self.userAddress else { return }
completion(myResult)

Which won't trigger the completion needed to update the label , instead you need

var callBack:((String)->())? 

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let userLocation: CLLocation = locations[0] as CLLocation
geoCoder.reverseGeocodeLocation(userLocation) { (placemarks, err) in
if let place = placemarks?.last{
callBack?(place.name!)
}
}
}

Then use

location.callBack = { [weak self] str in
print(str)
DispatchQueue.main.async { // reverseGeocodeLocation callback is in a background thread
// any ui
}
}

Update Label On Another View

use custom delegate method create a delegate in second view and access that view delegate function in first view. or use NSNotification or use NSUserdefaults

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

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:

  1. 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.
  2. LoadEvents does whatever it needs to and then upon finishing, calls onComplete and sends the results back through the closure.

Why is the label's text not updating?

Your outlets aren't wired up until the view loads.

Try creating a property in your KeysController, say:

var keyText: String?

Set the property after instantiating the view controller:

tipsVC.keyText = firstTips[initialRow]

Then in your KeysController.viewDidLoad method:

key1.text = keyText


Related Topics



Leave a reply



Submit