UILabel doesn't update after button click

2019-07-25 13:47发布

Background Xcode Version 8.2 (8C38)
ios 10.2 for iPhone 7 plus - Simulator

I got this working in objective-c and I'm now trying to get it to work in swift. The code compiles and runs, but when the buttons are clicked the label doesn't change. If the label was updating I would post it on CR rather than here.

It started out as an interview take home question, but the objective-c portion got me the onsite interview. I'm pursing this to learn more about swift. Interview question

/*
 * Create a static library or iOS Framework using Objective-C that performs the following 3 functions:
 * - Collects the GPS location (latitude and longitude) of the user at a point in time
 * - Collects the battery state and returns whether or not the device is plugged in and what percentage of life is left.
 * - Accesses any publicly available, free API to collect the data of your choice and returns it
 *        (this should be a network call)
 *
 * Build a simple application with 3 buttons and a label where text can be displayed.  Each button should call into the
 * three functions of the library described above and output the response to the label.  Your application should consist 
 * of two tabs, one written in Objective-C and one written in Swift.  Both tabs should call into the same Objective-C
 * library and perform the same function. 
 *
 * Only use Apple frameworks to complete this task. Fully comment your code explaining your logic and choices where multiple
 * choices are available. For example, Apple provides numerous ways to retrieve a network resource, document why you choose
 * the solution you did.
 */

This may be a duplicate question, but I'm not sure I have looked at The following questions:

I'm not sure if the problem is in the async call I make, in my creation of the button itself, or the property/var declarations at the top.

import UIKit

class ViewController: UIViewController {
    var getGPSLongitudeAndLatitudeWithTimeStamp : UIButton?
    var getBatteryLevelAndState : UIButton?
    var getNextorkImplementation : UIButton?
    var displayButtonAction : UILabel?

    // The following variables are used in multiple functions. They are constant during the display of the super view
    // and control the size of the subviews
    var selfWidth : CGFloat = 0.0
    var buttonHeight : CGFloat = 0.0
    var viewElementWidth : CGFloat = 0.0
    var buttonYCenterOffset : CGFloat = 0.0       // The y center should be half the value of the height
    var buttonXCenter : CGFloat = 0.0             // Center the button title relative to the width of the button and the width of the super view
    var buttonXInit : CGFloat = 0.0
    var buttonVerticalSeparation : CGFloat = 0.0
    var startingVerticalLocation : CGFloat = 0.0
    var displayLabelHeight: CGFloat = 50.0

    func initFramingValuesOfMyDisplay() {
        selfWidth = self.view.bounds.size.width
        buttonHeight = 20.0               // This should be programmable in relative to self.view.bounds.size.height
        viewElementWidth = 0.8 * selfWidth;
        buttonYCenterOffset = buttonHeight / 2.0; // The y center should be half the value of the height
        buttonXCenter = selfWidth / 2.0;   // Center the button title relative to the width of the button and the width of the super view
        buttonXInit = 0.0;
        buttonVerticalSeparation = buttonHeight + buttonYCenterOffset;
        startingVerticalLocation = 430.0;  // 430 was chosen based on experimentation in the simulator
    }

    func setLabelWithGPSLatitudeAndLongitudeWithTimeStampData()
    {
        var actionString : String = "Testing Label Text"

        actionString = "GPS Button Action Failure: Data Model not created";

        DispatchQueue.global().async {
            self.displayButtonAction?.text = actionString
        }
    }

    func setLabelWithBatteryLevelAndState() {
        var actionString : String = "Get Battery Level and State";

        actionString = "Battery Button Action Failure: Data Model not created"

        DispatchQueue.global().async {
            self.displayButtonAction?.text = actionString
        }
    }

    func setLabelActionNetwork() {
        var actionString :String = "Fake Button set to American Express Stock Price";

        actionString = "Network Button Action Failure: Data Model not created";

        DispatchQueue.global().async {
            self.displayButtonAction?.text = actionString
        }
    }

    func makeAButton(yButtonStart : CGFloat, buttonTitle: String, underSubview: UIButton?) -> UIButton
    {
        let thisButton = UIButton.init(type: .system)
        thisButton.frame = CGRect(x: buttonXInit, y: yButtonStart, width: viewElementWidth, height: buttonHeight)
        thisButton.setTitle(buttonTitle, for:UIControlState.normal)

        thisButton.backgroundColor = UIColor.yellow
        thisButton.setTitleColor(UIColor.black, for: UIControlState.normal)
        if ((underSubview) == nil) {
            self.view.addSubview(thisButton)
        }
        else {
            self.view.insertSubview(thisButton, belowSubview:underSubview!)
        }

        return thisButton;
    }

    func makeALabel(yLabelStart : CGFloat, underSubview: UIButton?) -> UILabel
    {
        let thisLabel = UILabel.init()
        thisLabel.frame = CGRect(x: selfWidth * 0.1, y: yLabelStart, width: viewElementWidth, height: displayLabelHeight)

        thisLabel.font = thisLabel.font.withSize(12)
        thisLabel.lineBreakMode = .byWordWrapping;
        thisLabel.numberOfLines = 0;
        thisLabel.textAlignment = NSTextAlignment.center;
        thisLabel.textColor = UIColor.black;

        if ((underSubview) == nil) {
            self.view.addSubview(thisLabel)
        }
        else {
            self.view.insertSubview(thisLabel, belowSubview:underSubview!)
        }

        return thisLabel;
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.view.backgroundColor = UIColor.white
        initFramingValuesOfMyDisplay()
        addButtonAndLabels()
    }

    func addButtonAndLabels() -> Void {
        if (selfWidth < 1.0) {
            return;
        }
        var viewElementVerticalLocation: CGFloat = startingVerticalLocation;
        // var touchUpInside : UIControlEvents = touchUpInside;

        let getGPSLongitudeAndLatitudeWithTimeStamp : UIButton = makeAButton(yButtonStart: viewElementVerticalLocation, buttonTitle: "Get GPS Location with TimeStamp", underSubview: nil)
        getGPSLongitudeAndLatitudeWithTimeStamp.addTarget(self, action: #selector(setLabelWithGPSLatitudeAndLongitudeWithTimeStampData), for:  .touchUpInside)
        viewElementVerticalLocation += buttonVerticalSeparation

        let getBatteryLevelAndState : UIButton = makeAButton(yButtonStart: viewElementVerticalLocation, buttonTitle: "Get Battery Level and State", underSubview: getGPSLongitudeAndLatitudeWithTimeStamp)
        getBatteryLevelAndState.addTarget(self, action: #selector(setLabelWithBatteryLevelAndState), for:  .touchUpInside)
        viewElementVerticalLocation += buttonVerticalSeparation

        let getNextorkImplementation : UIButton = makeAButton(yButtonStart: viewElementVerticalLocation, buttonTitle: "Get Battery Level and State", underSubview: getBatteryLevelAndState)
        getNextorkImplementation.addTarget(self, action: #selector(setLabelWithBatteryLevelAndState), for:  .touchUpInside)
        viewElementVerticalLocation += buttonVerticalSeparation

        let displayButtonAction = makeALabel(yLabelStart: viewElementVerticalLocation, underSubview: getNextorkImplementation)
        displayButtonAction.text = "No Action Yet"
    }

    required init(coder aDecoder: NSCoder) {
        super.init(nibName: nil, bundle: nil)
        initFramingValuesOfMyDisplay()
    }
}

3条回答
你好瞎i
2楼-- · 2019-07-25 14:09

You can't update UI elements on background (async) threads. You need to get the main thread to do that.

DispatchQueue.main.async(execute: {
    self.label.text = "some text"
})
查看更多
欢心
3楼-- · 2019-07-25 14:14

Your displayButtonAction is in fact nil. Notice in your method addButtonAndLabels() you are creating a label and populating the text only within the scope of that method here:

let displayButtonAction = makeALabel(yLabelStart: viewElementVerticalLocation, underSubview: getNextorkImplementation)

In your makeALabel method, you are inserting that label as a subview of your view which is why it shows up when you run the application. Nowhere are you assigning that label to displayButtonAction.

In summary, You are creating a local scope UILabel, populating it's placeholder text, inserting it as a subview of your view, then discarding it, then trying to populate the text of your displayButtonAction label on button press of which is nil.

Within addButtonAndLabels(), assigning the instantiated label to your main label in the main scope of the view controller via:

self.displayButtonAction = displayButtonAction

will get you started in the right direction.

Using the debugger tools, breakpoints, and po are your friends in situations like these.

查看更多
兄弟一词,经得起流年.
4楼-- · 2019-07-25 14:15

The main thing I notice is that you are trying to set the text for the label on a background queue. You should never make UI changes on anything but the main queue, so change that to the main queue like so:

DispatchQueue.main.async {
        self.displayButtonAction?.text = actionString
    }

You should be getting warnings in the debugger telling you this too.

You should also throw a breakpoint inside that function to verify that displayButtonAction is not nil, and that it has a frame that is actually on screen and has a non-zero size. Make good use of that debugger for these.

查看更多
登录 后发表回答