iOS label does not update text with function in Swift [duplicate] - ios

This question already has answers here:
update text field ui in swift ios
(2 answers)
Closed 8 years ago.
This seemingly simple issue is driving me crazy... I am playing around with SwiftyJSON to grab remote data and here is a snippet from my ViewController class in Swift:
override func viewDidLoad() {
super.viewDidLoad()
self.statusLabel.text = "welcome"
RemoteDataManager.getStatusUpdateFromURL { (statusData) -> Void in
let json = JSON(data: statusData)
self.statusLabel.text = "this does not work"
self.statusLabel.text = self.getMostRecentStatusUpdate(json) // also does not work
}
}
The statusLabel text is set to "welcome" but does not change afterwards. Funny though, anything I put inside func getMostRecentStatusUpdate(_:) with println() is printed to the console correctly, even if it comes from the remote json source (i.e. I know that this function works). My problem is that I cannot get the text printed to a UILabel instead of the console. I do not get any error messages.
I am not yet really familiar with the sort of Swift function like MyClass.myMethod { (myData) -> Void in .... } and I don't understand what's going wrong here. Any ideas?

UIKit is not thread safe and should only be updated from the main thread. Downloads are done on background thread, and you cannot update UI from there. Try:
override func viewDidLoad() {
super.viewDidLoad()
self.statusLabel.text = "welcome"
RemoteDataManager.getStatusUpdateFromURL { (statusData) -> Void in
let json = JSON(data: statusData)
dispatch_async(dispatch_get_main_queue()) {
self.statusLabel.text = "this does not work"
self.statusLabel.text = self.getMostRecentStatusUpdate(json) // also does not work
}
}
}

Related

Today Widget unable to load with a specific line of code

I've added a Today extension to my app and it all works fine until a specific line of code is compiled. NB: compiled, not executed!
My TodayViewController is:
class StoredDoses {
func getDoses(doses: inout [Dose]) {
if let userD = UserDefaults(suiteName: "com.btv.mySuite") {
if let dosesData = userD.object(forKey: "doses_key") {
do {
// -----------------------------------------------
// Comment the line below out and the widget works
doses = try PropertyListDecoder().decode([Dose].self, from: dosesData as! Data)
// -----------------------------------------------
} catch {
print ("ERROR")
}
}
}
}
}
class TodayViewController: UIViewController, NCWidgetProviding {
#IBOutlet weak var aText: UILabel!
#IBOutlet weak var bText: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view from its nib.
}
func widgetPerformUpdate(completionHandler: (#escaping (NCUpdateResult) -> Void)) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
//Just for development stage - not real, final code
let form = DateFormatter()
form.timeStyle = .short
aText.text = form.string(from: Date())
completionHandler(NCUpdateResult.newData)
}
}
So, the above code isn't well written, but it's what I've used to finally narrow down the cause of the unloading widget. The array of Doses is a custom, codable class, but if I try to get an array of String then it's the same. The StoredDoses code is included in the main app and doesn't cause any problems.
Just to re-iterate: I'm not trying to execute any method in the StoredDoses class. I don't even have an instance of it in the widget. When the doses = ... line is merely commented out then the widget loads and the aText label in the widget appears with the current time in it.
Ok, so thanks to #Chris' apparently unconnected advise I got it sorted!
It appears to have been an Interface Builder issue: somehow it had retained the original name of the UILabel that was auto-created when I added the Today extension in Xcode. At some point, after connecting an IBOutlet to the label with "Hello World" in it, I'd renamed it to something slightly more relevant but hadn't unconnected it before over-typing the new name in the TodayViewController.
The console didn't throw up any problems and at times seemed to work, but when the line with
try PropertyListDecoder().decode([Dose].self, from: dosesData as! Data)
was present then it stopped working without any console messages.
I only found that out after I explored #Chris comment about the as! Data. I re-wrote to first get the Data:
if let userD = UserDefaults(suiteName: "com.btv.mySuite") {
if let dosesData = userD.object(forKey: "doses_key") {
if let unwrappedData = dosesData as? Data {
do {
doses = try PropertyListDecoder().decode([SplitDose].self, from: unwrappedData)
} catch {
doses.removeAll()
}
}
}
}
Once this was compiled (remember, it's still not being executed - this is just sitting there waiting to be used) the console threw up a message and the app crashed out showing the old UILabel name as not key-compliant. Reconnecting the UILabel in IB fixed everything and I could compile the original code....
This probably deserves a Radar entry but right now I don't want to waste another day re-creating (if at all possible) this problem!

Swift FireStore Listener throws error when loading application the second time

Hi I am in desperate need for some help
All this is happening in a UIViewController child class
I am currently attaching the listener and populating an array and then feeding it to a UICollectionView in the following function (excuse some of the cut off code):
fileprivate func fetchNotes() { // This function is called in vidDidLoad()
let db = Firestore.firestore()
// Attaching listener (ie. listener is an attribute of the class)
listener = db.collection("Courses").document(titleForNavBar).collection("Notes")
.addSnapshotListener { snapshot, error in
// checking for any error
if error != nil {
self.arrayOfNotes.removeAll()
self.allNotesView.arrayOfNotes = self.arrayOfNotes
DispatchQueue.main.async {
self.allNotesView.allNotesCollectionView.reloadData()
}
return
} else {
self.arrayOfNotes.removeAll()
// if there is no error, the array holding all the objects is populated, in a for..loop
for document in (snapshot?.documents)! {
if let noteName = document.data()["noteName"] as? String,
let lectureInformation = document.data()["lectureInformation"] as? String,
let noteDescription = document.data()["noteDescription"] as? String,
let forCourse = document.data()["forCourse"] as? String,
let storageReference = document.data()["storageReference"] as? String,
let noteSize = document.data()["noteSize"] as? Int,
let rating = document.data()["rating"] as? Int
{
self.arrayOfNotes.append(Note(forCourse: forCourse, lectureInformation: lectureInformation, noteDescription: noteDescription, noteName: noteName, noteSize: noteSize, rating: rating, storageReference: storageReference))
self.allNotesView.arrayOfNotes = self.arrayOfNotes
// reloading the UICollectionView (on the main thread) so that it displays new data
DispatchQueue.main.async {
self.allNotesView.allNotesCollectionView.reloadData()
}
}
}
}
}
}
When the view disappears, I am also removing the listener
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
if listener != nil {
listener.remove()
}
print("listener removed")
}
This works fine, when I install the application for the first time on any device or simulator. When I try to launch the controller, the second time, I get a very nasty error that I have no idea how to debug.
To be accurate the console throws this error:
NoteShare[97230:10528984] *** Assertion failure in -[FSTLevelDBRemoteDocumentCache decodedMaybeDocument:withKey:], third_party/firebase/ios/Source/Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.mm:152
I know this question is quite long (sorry about that), but have you guys come across this error. Please give some hint on how to solve this problem. Thanks! If you need to see any other piece of my code, please let me know.
It seems to be failing here. I don't see what you could be doing wrong in your code to cause that, so you may have hit a bug. It seems very similar to this issue, which has been fixed in the repo but not been released.

Thread 1: EXC_BAD_INSTRUCTION (code = EXC_1386_INVOP, subside = 0x0) in Xcode [duplicate]

This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 5 years ago.
I've got this code :
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myLabel: UILabel!
#IBAction func myFirstButtonPressed(_ sender: UIButton) {
let getButtonText : String = sender.title(for: .normal)!
myLabel.text = "Clicked \(String(describing: getButtonText))"
}
#IBAction func mySecondButtonPressed(_ sender: UIButton) {
let getSecondTitle :String = sender.title(for: .normal)!
myLabel.text = "Clicked \(String (describing: getSecondTitle))"
}}
As you can see I've created two buttons and a label and I've used the exact same instructions on them,
But when I ran the simulator on Xcode, I tried clicking the second button and the label changed perfectly without any problems.
But I when I clicked the First Button, the error occurs.
It is the exact same code but why is the first button giving me an error and the second button isn't?
I'm just starting to learn IOS, and the error came after the Thread : SIGBRT when I "continue program execution".
I've seen many other people posting this problem here on stack overflow but I couldn't find a solution to the problem.
Hope you guys can help me.
You should not force unwrap the value coming from sender.title. Instead you should do something like:
if let text = sender.title() {
print(text)
}
That may cause the text not to show up (because something else may be wrong in your setup), but at least it will prevent the hard crash.
Might be this line of code return the nil value.
let getButtonText : String = sender.title(for: .normal)!
So change force wrapping to optional
let getButtonText : String? = sender.title(for: .normal)?
Also check the reference and Action of button connection.

How to structure code to deal with asynchronous Firebase snapshot? [duplicate]

This question already has answers here:
Returning method object from inside block
(3 answers)
Closed 5 years ago.
I have an problem that I can not solve. A lot of questions are in JS and I don't really understand them.
I'm using Firebase as my database for my IOS app and Swift. Here is my situation:
I have a class file that contains functions that can retrieve values in my database. I'm calling these functions in some viewControllers.
The values retrieved by these functions are immediately used in these viewControllers.
My problem is that my app crash because of nil values returned by the class file functions. This happen because of the asynchronous Firebase snapshot.
The variables assumed to contain the values are used before their value is assigned => My app crash, or printed values are nil.
Then my question is simple: How can I structure my code to avoid this issue? I already tried completions, but that's not working for me: functions are still asynchronous.
Here is one of my function in the class file:
func initAverageMark(completionHandler: #escaping (_ mark: Double) -> ()) {
let userRef = ref.child("users").child((user?.uid)!).child("mark")
userRef.observeSingleEvent(of: .value, with: { (snapshot) -> Void in
if let mark: Double = snapshot.value as? Double {
completionHandler(mark)
}
}) { (error) in
print(error.localizedDescription)
}
}
One of my viewController code:
private var totalAsks: Double!
override func viewDidLoad() {
super.viewDidLoad()
initInfos()
}
func initInfos() {
mainUser().initTotalAsks{ total in
self.totalAsks = total
}
initLabels()
}
func initLabels() {
totalAsksLabel.text = " \(totalAsks!)" // it crashs here
}
Assuming you'd want to set some label or something in your viewController to the value of mark you'd do it like this.
mainUser().initTotalAsks { mark in
self.totalAsksLabel.text = " \(mark)"
}
Edit
Or if you absolutely want to use that Double.
private var totalAsks: Double? = nil {
didSet {
initLabels()
}
}
override func viewDidLoad() {
super.viewDidLoad()
initInfos()
}
func initInfos() {
mainUser().initTotalAsks{ total in
self.totalAsks = total
}
}
func initLabels() {
guard totalAsks != nil else {
return
}
totalAsksLabel.text = " \(totalAsks!)"
}

(Xcode 6 beta / Swift) performSegueWithIdentifier has delay before segue

I'm just learning Ios programming for the first time, with Swift and Xcode 6 beta.
I am making a simple test app that should call an API, and then segue programmatically to a different view to present the information that was retrieved.
The problem is the segue. In my delegate method didReceiveAPIResults, after everything has been successfully retrieved, I have:
println("--> Perform segue")
performSegueWithIdentifier("segueWhenApiDidFinish", sender: nil)
When the app runs, the console outputs --> Perform segue, but then there is about a 5-10 second delay before the app actually segues to the next view. During this time all the UI components are frozen.
I'm a little stuck trying to figure out why the segue doesn't happen immediately, or how to debug this!
Heres The Full View controller:
import UIKit
class ViewController: UIViewController, APIControllerProtocol {
#lazy var api: APIController = APIController(delegate: self)
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func didReceiveAPIResults(results: NSDictionary) {
println(results)
println("--> Perform segue")
performSegueWithIdentifier("segueWhenApiDidFinish", sender: nil)
}
#IBAction func getData(sender : AnyObject){
println("--> Get Data from API")
api.getInfoFromAPI()
}
}
And my API controller:
import UIKit
import Foundation
protocol APIControllerProtocol {
func didReceiveAPIResults(results: NSDictionary)
}
class APIController: NSObject {
var delegate: APIControllerProtocol?
init(delegate: APIControllerProtocol?) {
self.delegate = delegate
}
func getInfoFromAPI(){
let session = NSURLSession.sharedSession()
let url = NSURL(string: "https://itunes.apple.com/search?term=Bob+Dylan&media=music&entity=album")
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
if(error) {
println("There was a web request error.")
return
}
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions. MutableContainers, error: &err) as NSDictionary
if(err?) {
println("There was a JSON error.")
return
}
self.delegate?.didReceiveAPIResults(jsonResult)
})
task.resume()
}
}
UPDATE: Got this working based on Ethan's answer. Below is the exact code that ended up getting the desired behavior. I needed assign that to self to have access to self inside the dispatch_async block.
let that = self
if(NSThread.isMainThread()){
self.delegate?.didReceiveAPIResults(jsonResult)
}else
{
dispatch_async(dispatch_get_main_queue()) {
println(that)
that.delegate?.didReceiveAPIResults(jsonResult)
}
}
Interestingly, this code does not work if I remove the println(that) line! (The build fails with could not find member 'didReceiveAPIResults'). This is very curious, if anyone could comment on this...
I believe you are not on the main thread when calling
self.delegate?.didReceiveAPIResults(jsonResult)
If you ever are curious whether you are on the main thread or not, as an exercise, you can do NSThread.isMainThread() returns a bool.
Anyway, if it turns out that you are not on the main thread, you must be! Why? Because background threads are not prioritized and will wait a very long time before you see results, unlike the mainthread, which is high priority for the system. Here is what to do... in getInfoFromAPI replace
self.delegate?.didReceiveAPIResults(jsonResult)
with
dispatch_sync(dispatch_get_main_queue())
{
self.delegate?.didReceiveAPIResults(jsonResult)
}
Here you are using GCD to get the main queue and perform the UI update within the block on the main thread.
But be wear, for if you are already on the main thread, calling dispatch_sync(dispatch_get_main_queue()) will wait FOREVER (aka, freezing your app)... so be aware of that.
I have a delay problem with segue from a UITableView. I have checked and I appear to be on the main thread. I checked "NSThread.isMainThread()" during prepareForSegue. It always returns true.
I found a solution on Apple Developer forums! https://forums.developer.apple.com/thread/5861
This person says it is a bug in iOS 8.
I followed their suggestion to add a line of code to didSelectRowAtIndexPath...... Despatch_async.....
It worked for me, hopefully you too.

Resources