【问题标题】:Modify View Controller Properties Before View Controller is Displayed在显示视图控制器之前修改视图控制器属性
【发布时间】:2017-03-17 17:41:12
【问题描述】:

我即将完成我的第一个 iOS 应用,但我遇到了一个我不知道如何解决的问题。

应用程序的初始 ViewController 包含一个 MKMapView,它几乎没有注释。单击任何注解时,会出现一个弹出窗口,其中包含一些信息,然后,有一个信息按钮可通向一个新的 ViewController,其中包含与所选注解相关的详细信息。

问题是第二个 ViewController(DetailedViewController),有几个标签(标题、描述)、一个图像和几个链接,应该在 ViewController 本身显示之前加载。我正在从 JSON 中读取这些值,但没有问题。

我正在读取所有值并在 ViewDidLoad() 中设置它们

所以问题是,当第二个 ViewController(DetailedViewController) 加载到模拟器或物理 iPhone 中时,所有字段都有其默认值(故事板中设置的那个)并花费几秒钟来更新所需的值,即使从 JSON 中读取它们的速度如此之快(根据日志)。

另外,控制台中出现错误:

This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
 Stack:(
    0   CoreFoundation                      0x000000018a0ca1d8 <redacted> + 148
    1   libobjc.A.dylib                     0x0000000188b0455c objc_exception_throw + 56
    2   CoreFoundation                      0x000000018a0ca108 <redacted> + 0
    3   Foundation                          0x000000018acb1ea4 <redacted> + 192
    4   Foundation                          0x000000018acb1be4 <redacted> + 76
    5   Foundation                          0x000000018aafd54c <redacted> + 112
    6   Foundation                          0x000000018acb0880 <redacted> + 112
    7   UIKit                               0x000000018ff2140c <redacted> + 1688
    8   QuartzCore                          0x000000018d3e1188 <redacted> + 148
    9   UIKit                               0x000000019056de90 <redacted> + 64
    10  QuartzCore                          0x000000018d3d5e64 <redacted> + 292
    11  QuartzCore                          0x000000018d3d5d24 <redacted> + 32
    12  QuartzCore                          0x000000018d3527ec <redacted> + 252
    13  QuartzCore                          0x000000018d379c58 <redacted> + 512
    14  QuartzCore                          0x000000018d37a124 <redacted> + 660
    15  libsystem_pthread.dylib             0x000000018915efbc <redacted> + 572
    16  libsystem_pthread.dylib             0x000000018915ece4 <redacted> + 200
    17  libsystem_pthread.dylib             0x000000018915e378 pthread_mutex_lock + 0
    18  libsystem_pthread.dylib             0x000000018915dda4 start_wqthread + 4
)

我读过类似的问题,他们在谈论这样的事情:

dispatch_async(dispatch_get_main_queue(){
    // code here
})

但是我不知道具体怎么用 :(

谁能帮帮我?

提前非常感谢你:)

编辑:

我将添加我的详细视图控制器的代码,以更准确地解释发生了什么:

class BarDetailsViewController: UIViewController {

@IBOutlet var BarDetailsView: UIView!
@IBOutlet weak var BarNameLabel: UILabel!
@IBOutlet weak var BarDescriptionLabel: UILabel!

@IBOutlet weak var BarImage: UIImageView!

var barPhoneNumber: AnyObject! = "anyBarPhoneNumber"
var barWebsite: AnyObject! = "anyBarWebsite"
var barName: AnyObject! = "anyBarName"

var pinCoordinates : CLLocationCoordinate2D!
var pinId : String!

override func viewDidLoad() {
    super.viewDidLoad()

    let jsonURL: String = "http://www.craftbr.me/services/getbiz/index.php?barid=" + self.pinId
    let requestURL: NSURL = NSURL(string: jsonURL)!

    let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
    let session = NSURLSession.sharedSession()
    let task = session.dataTaskWithRequest(urlRequest) {
        (data, response, error) -> Void in

        let httpResponse = response as! NSHTTPURLResponse
        let statusCode = httpResponse.statusCode

        if (statusCode == 200) {
            do{
                let json: AnyObject! = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)

                let barName: AnyObject! = json["name"]
                self.BarNameLabel.text = String(barName)
                self.barName = String(barName)

                self.BarDescriptionLabel.text = (json["description"] as! String)

                self.barPhoneNumber = json["telf"]

                self.barWebsite = json["website"] as! String

                let barImgUrl: AnyObject! = json["imgurl"]
                let barImgUrlString: String! = String(barImgUrl)

                if let imageUrl  = NSURL(string: barImgUrlString),
                    let data = NSData(contentsOfURL: imageUrl)
                {
                    self.BarImage.image = UIImage(data: data)
                }

            }catch {
                print("Error with Json: \(error)")
            }
        }
    }  
    task.resume()
}

}

因此,在这里,在 ViewDidLoad() 内部,我使用从 json 获得的信息来修改 Storyboard 中创建的标签内容。

问题是从 json 中获取信息是如此之快,但是,修改标签是一件很慢并引发错误的事情。

那么@fragilecat,我应该在哪里添加该功能?以及如何声明 DispatchQueue?我已经尝试过了,但它不起作用。而这个函数,应该在哪里调用呢?

再次感谢您的帮助:)

【问题讨论】:

标签: ios swift layout autolayout viewcontroller


【解决方案1】:

Apple 使用队列来表示线程,正如 @Adam 在 cmets 中所说,您应该查看 Concurrency Programming Guide 以更好地了解您的应用程序的行为。

但是,解释一下您的应用程序发生了什么导致崩溃可能会很有用,因为它是 iOS 中非常常见的模式。

您正在向某个网络服务发出请求。除非明确地这个请求将在后台队列上发生。如果不是这种情况,您的应用程序的 UI 将在向 Web 服务发出请求时冻结。这里要记住的一点是,所有的 ui 工作都必须在主队列上完成! @Leon Guo 向您展示了如何从不同的队列访问主队列。

那么这对您的网络服务请求有何影响。好吧,当响应返回并解析它并准备好将数据分配给标签和文本字段等 UI 元素时,您希望在主队列上执行此操作,如下所示:

// comment: example method on DetailedViewController class
func prepareUI(data: MyDataType){
    // Swift 3
    DispatchQueue.main.async {
        titleLabel.text = data.titleString
        descriptionLabel.text = data.descriptionText
    }
}

否则,您将从主队列以外的队列访问DetailedViewController 中的ui 元素,从而导致崩溃。因此,要立即解决您的问题,请将您对 UI 元素的分配包装在 DispatchQueue.main.async 代码块中,无论您在做什么。这应该可以让您重新启动并再次运行。

从长远来看,您需要了解如何在 iOS 中实现并发。 The App Programming Guide for iOS 是一个很好的起点!

编辑

这是您使用 dispatch_async(dispatch_get_main_queue()) 函数的代码。我会说这只是为了让你启动并运行......

import UIKit

class BarDetailsViewController: UIViewController {

    @IBOutlet var barDetailsView: UIView!
    @IBOutlet weak var barNameLabel: UILabel!
    @IBOutlet weak var barDescriptionLabel: UILabel!

    @IBOutlet weak var BarImage: UIImageView!

    var barPhoneNumber  = "anyBarPhoneNumber"
    var barWebsite = "anyBarWebsite"
    var barName = "anyBarName"

    var pinCoordinates : CLLocationCoordinate2D!
    var pinId : String!

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // For example purposes only
        networkCall()

    }



    func networkCall() {
        let jsonURL: String = "http://www.craftbr.me/services/getbiz/index.php?barid=" + self.pinId
        let requestURL: NSURL = NSURL(string: jsonURL)!

        let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
        let session = NSURLSession.sharedSession()
        let task = session.dataTaskWithRequest(urlRequest) {
            (data, response, error) -> Void in

            let httpResponse = response as! NSHTTPURLResponse
            let statusCode = httpResponse.statusCode

            if (statusCode == 200) {
                do{

                    let json: AnyObject = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)

                    // Here we get back on the main queue since we are altering UI elements
                    dispatch_async(dispatch_get_main_queue()) {
                        let barName: AnyObject! = json["name"]
                        self.barNameLabel.text = String(barName)
                        self.barName = String(barName)

                        if let text = json["description"] as? String {
                            self.barDescriptionLabel.text = text
                        }

                        if phoneNumber = json["telf"] as? String {
                            self.barPhoneNumber = phoneNumber
                        }

                        if webSite = json["website"] as? String {
                            self.barWebsite =  barWebsite
                        }

                        let barImgUrl: AnyObject = json["imgurl"]
                        let barImgUrlString: String = String(barImgUrl)

                        if let imageUrl  = NSURL(string: barImgUrlString), let data = NSData(contentsOfURL: imageUrl) {
                            self.BarImage.image = UIImage(data: data)
                        }
                    }


                }catch {
                    print("Error with Json: \(error)")
                }
            }
        }
        task.resume()
    }

}

【讨论】:

  • 感谢您的回答。我编辑了帖子添加了更多信息。我应该在哪里调用这个函数?以及如何声明 DispatchQueue?我试过了,但我不能让它工作:(
  • 我去看看。
  • 现在来看看吧。
  • 你好。谢谢,通过您的代码,我对它的工作原理的理解得到了改进:) 我已经将结构回复到我的代码中,了解它是如何工作的,但是在我们调用 DispatchQueue 的行中:DispatchQueue.main.async { 出现错误说 使用未解析的标识符“DispatchQueue”。阅读具有相同错误的帖子,我在我的 swift 文件的顶部添加了一个 Import Dispatch,所以现在,我有以下Import Foundation Import UIKit Import MapKit Import Dispatch。但是错误仍然出现,它无法解析 DispatchQueue。有问题?谢谢
  • 你知道你用的是什么版本的 Swift 吗?
【解决方案2】:

所有关于UI的更新操作都必须在主线程中完成,比如在视图控制器中修改视图的背景,可以这样:

    DispatchQueue.main.async {
        self.view.backgroundColor = UIColor.red
    }

【讨论】:

  • GCD API 在 Swift 3.0 中有一些变化
  • 首先,感谢您的快速回答 :) 原谅我的无知,但我在 iOS 开发方面没有那么经验,我有点困惑。你说的是主线程,你的意思是我应该把这段代码放在DetailedViewController中,但是放在ViewDidLoad函数之外?以及如何声明 DispatchQueue?再次感谢您的帮助:)
猜你喜欢
  • 1970-01-01
  • 2012-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-12
  • 2011-12-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多