单击按钮后UILabel不会更新

时间:2017-04-25 17:44:10

标签: ios swift asynchronous

背景Xcode版本8.2(8C38)
适用于iPhone 7 plus的ios 10.2 - 模拟器

我让它在objective-c中工作,我现在试图让它在swift中工作。代码编译并运行,但单击按钮时标签不会更改。如果标签正在更新,我会将其发布在CR而不是此处。

它最初是作为一个采访带回家的问题,但是目标c部分让我接受了现场采访。我正在追求这一点,以了解更多有关斯威夫特的信息。面试问题

/*
 * 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.
 */

这可能是一个重复的问题,但我不确定我是否已经查看过 以下问题:

我不确定问题出在我所做的异步调用中,是在创建按钮本身,还是在顶部的属性/ var声明中。

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 个答案:

答案 0 :(得分:3)

您的displayButtonAction实际上是零。请注意,在您的方法addButtonAndLabels()中,您正在创建一个标签,并仅在该方法的范围内填充文本:

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

makeALabel方法中,您将该标签作为视图的子视图插入,这就是运行应用程序时显示的原因。您无处将该标签分配给displayButtonAction

总之,您要创建一个本地范围UILabel,填充它的占位符文本,将其作为视图的子视图插入,然后丢弃它,然后尝试填充displayButtonAction标签的文本按下按钮为nil

addButtonAndLabels()内,通过以下方式将实例化标签分配给视图控制器主范围内的主标签:

self.displayButtonAction = displayButtonAction

将帮助您开始正确的方向。

在这种情况下,使用调试工具,断点和po是您的朋友。

答案 1 :(得分:2)

我注意到的主要是你试图在后台队列上设置标签的文本。除了主队列之外,您不应该对除了主队列之外的任何内容进行UI更改,因此将其更改为主队列,如下所示:

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

您应该在调试器中收到警告,告诉您这一点。

你还应该在该函数中抛出一个断点来验证displayButtonAction不是nil,并且它有frame实际上在屏幕上且大小非零。充分利用这个调试器。

答案 2 :(得分:1)

您无法在后台(异步)线程上更新UI元素。您需要获取线程才能执行此操作。

DispatchQueue.main.async(execute: {
    self.label.text = "some text"
})