UILabel text setting causes EXC_BAD_INSTR EXC_i386_INVOP - ios

Currently working on an iOS app in Swift, and I have a UILabel created in Storyboard control click linked to my View Controller, but whenever I try to access and set the text of this label, I get a EXC_BAD_INSTRUCTION. The code is being called from a PFArrayResultBlock, so I'm not sure if it's a self issue, but here is the block I'm using:
let block : PFArrayResultBlock = { (array: [AnyObject]!, error: NSError!) in
if(array.count == 0) {
self.setupBankInfoViews()
self.isEnteringBankInfo = true
} else {
var credits : String = PFUser.currentUser().objectForKey("credits") as! String
var text = "You have "
text += credits
text += " credits. Cash out now for XXX dollars"
self.creditLabel.text = text
self.isEnteringBankInfo = false
if(self.routingNumberTextField != nil) {
self.routingNumberTextField.removeFromSuperview()
}
if(self.accountNumberTextField != nil) {
self.accountNumberTextField.removeFromSuperview()
}
self.cashOutButton.setTitle("Cash Out", forState: UIControlState.allZeros)
}
}
The error seems to be occurring on the line with self.creditLabel.text = text, and never gets past that. My creditLabel is defined as such:
#IBOutlet weak var creditLabel: UILabel!
Not quite sure what's going on here, this code seemed to work a couple days ago and I came back to it today and it started crashing. Any help would be appreciated.

Try restarting Xcode. Also, recheck your storyboard to make sure the label declaration is linked to the storyboard visual object...

Related

Delete NSObject from model

I have created a file in Swift called infoModel. This is used to pass info from one ViewController to another, and the info is written in a label. This works perfectly, but I want to be able to delete that info, or at least remove it from the label.
I have tried to set label = nil, but the info pops back in when I change to a different viewController and back.
How should I go about to remove the data?
Code infoModel:
import UIKit
class infoModel: NSObject {
var info: String = ""
var info2: String = ""
var info3: String = ""
var info4: String = ""
func currentInfo() -> String {
//return a string with the current info
return info + "" + info2 + "" + info3 + "" + info4 + ""
}
}
And in view controller:
class collectViewController: UIViewController {
var myInfo = infoModel()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
infoLabel.text = myInfo.currentInfo()
}
}
It´s the infoLabel I want to remove from.
*Edit: The app is collecting info from users. The way that it does that, is simply that the user is pressing a/some button/buttons that represents the suitable values for that specific user. The information is then displayed in a different View Controller. If the user presses the wrong button, I want them to be able to delete/remove the information and start over. The removebutton is in the same View controller that the infoLabel is.
When all the information is correct, the user then pushes another button that writes to Firebase Database.
So my goal is that the user is able to delete/remove the info, and start over.
Don't set initial value to myInfo, make it an optional:
var myInfo: infoModel?
and
infoLabel.text = myInfo?.currentInfo()
Now if there is no model, nothing will be set.
Also, you can automatically update the label using:
var myInfo: infoModel? {
didSet {
_ = self.view // load the view if not loaded
infoLabel.text = myInfo?.currentInfo()
}
}
Now just setting myInfo = nil will clear the label.
you need to use the model as a container of your data and changes.
when the user presses the various buttons, you need to update the model, and when the user clicks the remove button, you need to update the model to reflect that state.
Instead of setting the entire object to nil, set the inside variables to nil, so that the model object always exist. You will need to do more unwrapping.
var info: String? = nil
var info2: String? = nil
var info3: String? = nil
var info4: String? = nil
If you don't want to do that, you will need to check the nullability of your model and create a new one when it is nil (as Sultan explained in the other response)
(I could also suggest you use a struct and not a class)
You then need to redraw your UI when the model is updated.
I think in your simple case even running setNeedsDisplay on the view of the controller should do the trick
I actually solved the issue with not being able to re add data to the label just by doing this:
myInfo?.info = ""
myInfo?.info2 = ""
myInfo?.info3 = ""
myInfo?.info4 = ""

iOS - Today extension widget cleared over time

Situation as it should be
We have a today widget that shows a maximum of 6 buttons depending on data set in the corresponding app. This data is shared using app-groups. If at least one button is configured it will show up as shown in the image above. If the user is not logged in, or if no buttons are configured, it will show a message as shown in the image below.
Problem
After several hours (somewhere between 4 and 7) of not having opened the app, the widget reverts to the 'No buttons configured' view.
Analysis so far
The way the data is loaded from the app-group is done using the code as shown below. (gist for full code) In the way I had written it, the only way the 'No buttons configured' view can be shown is if the buttons array actually exists but has a length of zero.
I expected something like a cache clearing or a background service stopping, but as far as I can see, exceptions should be caught earlier:
If no connection could be made to the app-group data, userDefaults should be nil, so it should show the 'Not logged in view'.
In case the buttons were never defined, buttons should be nil and so again it should show the 'Not logged in view'
Considering the app does nothing in the background, the app itself could not be changing the buttons.
I tried reproducing this while having the debugger connected, but the problem will not reproduce.
Does anyone even have the slightest idea on how to fix this issue or how to start debugging this?
Relevant files:
TodayViewController
Cordova Plugin
Relevant code:
private struct sharedData {
static var baseUrl: String?
static var token: String?
static var phoneDeviceId: String?
static var buttons: Array<Button>?
}
func loadData() {
let groupIdentifier = "group." + NSBundle.mainBundle().bundleIdentifier!
var groupIdArray = groupIdentifier.componentsSeparatedByString(".")
groupIdArray.removeAtIndex(groupIdArray.count - 1)
let appGroupIdentifier = groupIdArray.joinWithSeparator(".");
let userDefaults = NSUserDefaults.init(suiteName: appGroupIdentifier)
if (userDefaults == nil) {
print("Error in user defaults")
setButtonTitle("Not logged in. Open Triggi to continue.")
return false
}
sharedData.baseUrl = userDefaults?.valueForKey("baseUrl") as? String
sharedData.token = userDefaults?.valueForKey("token") as? String
sharedData.phoneDeviceId = userDefaults?.valueForKey("phoneDeviceId") as? String
let buttons = userDefaults?.valueForKey("buttons") as? NSArray
if (sharedData.baseUrl == nil || sharedData.token == nil || sharedData.phoneDeviceId == nil || buttons == nil) {
print("Missing data")
setButtonTitle("Not logged in. Open Triggi to continue.")
return false
}
if (buttons?.count == 0) {
print("No buttons configured")
setButtonTitle("No buttons configured. Open Triggi to continue.")
return false;
}
// More things are done with the data here
}
Today Extension Controller is a UIViewController and thus follows the same lifecycle as that of a UIViewController. So, the lifecycle method viewDidLoad() is called everytime whenever the widget is loaded.
Also, widgetPerformUpdateWithCompletionHandler is called:
when widget is updated in background
before widget snapshot is taken
So, instead of just calling loadData() in widgetPerformUpdateWithCompletionHandler, also call it from viewDidLoad().
Also, where have you written the code to add/remove UIButtons from superview and to show "No buttons configured" in your code?

Connected UILabel error EXC_BAD_EXCEPTION when trying to change text value

Something very strange has been happening when I change my UILabel's text in my view controller's viewDidLoad method. Although I am 100% certain that the label is connected (I have reconnected it multiple times as well as changed the name), it still gives me an EXC_BAD_EXCEPTION error when trying to change it. My code is below.
**NOTE: I should also mention that this error does not occur when the VC first initializes, but when I press a button that segues to another VC.
class BroadwayOrderReview: UIViewController, UITableViewDelegate, UITableViewDataSource, ClassNameDelegate {
#IBOutlet weak var BroadwayOrderReviewTableView: UITableView!
#IBOutlet weak var finalOrderPriceTotalLbl: UILabel!
var OrderDictionary: [String:String] = [:]
func addButtonAction(addedList:[String:Float],numOrders:[String:Int]) {
print(addedList)
print(numOrders)
}
override func awakeFromNib() {
}
override func viewDidLoad() {
super.viewDidLoad()
print("NUM ORDERS \(numOrders)")
self.finalOrderPriceTotalLbl.text = "0.00"
let totalPriceArray = Array(numOrders.keys).sort(<)
for (key) in totalPriceArray {
print("TOTAL PRICE ARRAY \(totalPriceArray)")
self.finalOrderPriceTotalLbl.text = String(Float(self.finalOrderPriceTotalLbl.text!)! + (Float(numOrders[key]!) * addedList[key]!))
print("TOTAL ORDER LBL \(finalOrderPriceTotalLbl.text)")
}
BroadwayOrderReviewTableView.delegate = self
BroadwayOrderReviewTableView.dataSource = self
for (name,orders) in numOrders {
print(name)
OrderDictionary["\(numOrders[name]!) \(name)"] = String(addedList[name]! * Float(numOrders[name]!))
}
print(OrderDictionary)
}
Thank you for any and all help, I really appreciate it.
You should not use a capital for naming your tableView.
BroadwayOrderReviewTableView should be broadwayOrderReviewTableView. You only want to use class, struct with first letter caps.
Did you try to delete your label from your code and storyboard?
It looks like the connection isn't there.
With what you shared it seems it can't access a value because it hasn't been initialized.
Something i didn't know when I started learning dev. you can actually type in the console log.
type po finalOrderPriceTotalLbl (for print out finalOrderPriceTotalLbl)
You can only use that when your app is on standby with breakpoint, and maybe crashes (I forgot for one sec) to setup a breakpoint just click on the number's line. to remove just drag it out.
If the print give you nil. you know there is a issue with your label connection for sure.

iOS label does not update text even from main thread

I've spent a fun couple of hours trying all sorts of different combinations to have a label properly update its title after a Firebase async download. It's the same issue raised here and here. Seems like a clear fix, but I'm doing something wrong and would appreciate any help pointing me in the right direction.
The basic flow is view loads, data is downloaded from Firebase, some labels are updated accordingly with downloaded data. One representative iteration I have tried is as follows:
// Query Firebase.
let detailsRef = self.ref.child("eventDetails")
detailsRef.queryOrdered(byChild: "UNIQUE_ID_EVENT_NUMBER").queryEqual(toValue: eventID).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("error")
}
else {
var tempDict = [NSDictionary]()
for child in snapshot.children {
let data = child as! FIRDataSnapshot
let dict = data.value as! NSDictionary as! [String:Any]
tempDict.append(dict as NSDictionary)
}
self.dictionaryOfRecoDetails = tempDict
self.ParseFirebaseData()
DispatchQueue.main.async {
// This is the function that updates labels and button text in format like self.websiteLabel.titleLabel?.text = "Appropriate String"
self.loadDataForView()
}
}
})
func loadDataForView() {
// Example of the label update that happens within this function.
// Do not show website button if there is no website.
if self.recommendation.recommendationWebsiteUrl == "" || self.recommendation.recommendationWebsiteUrl == nil || self.recommendation.recommendationWebsiteUrl == "NA" {
self.websiteLabel.titleLabel?.text = ""
self.websiteHeight.constant = 0
self.websiteBottom.constant = 0
}
else {
self.websiteLabel.titleLabel?.text = "Go to Website"
}
}
EDIT UPDATE: The call to the code above is coming from viewDidAppear(). It doesn't update if I call it from viewDidLayoutSubviews() either.
From debugging I know the label update is getting called, but nothing is changing. Feels like something simple I'm missing, but I'm stuck. Thanks for your ideas.
I'm pretty sure you don't need the DispatchQueue.main.async bit. Just try calling self.loadDataFromView() and see if that helps.
This ended up being a lesson in mis-labeling causing confusion. The label being changed actually isn't a label, but a button. Shouldn't have been named websiteLabel! Once the title was changed with self.websiteLabel.setTitle("Go to Website", for: .normal) then everything worked as expected.

Swift update UILabel with dispatch_async not working

Why doesn't this work?
dispatch_async(dispatch_get_main_queue(), {
self.timeStringLabel.text = "\(self.timeStringSelected)"
println(self.timeStringLabel.text)
})
I'm trying to update a label in Swift but the UI for the label never changes. I keep googling it, but I can't find any responses that don't use dispatch_async. What am I doing wrong?
1st Edit: I was mistaken. I'm not printing the updated text. The text never changes. It always prints out Optional("0") if that helps. The default value is 0 as defined in the Storyboard.
I have tried it with and without dispatch_async without any success. I also tried adding
self.timeStringLabel.setNeedsDisplay()
Immediately after updating the text, but that also doesn't work.
Edit 2: Here's the complete function + UILabel declaration
#IBOutlet weak var timeNumberLabel: UILabel!
#IBAction func timeNumberButtonPressed(sender: UIButton) {
println("Number Selected. Tag \(sender.tag)")
dispatch_async(dispatch_get_main_queue()) {
self.timeNumberOneButton.selected = false
self.timeNumberTwoButton.selected = false
self.timeNumberThreeButton.selected = false
self.timeNumberFourButton.selected = false
if sender.tag == 0{
self.timeNumberSelected = 0
} else if sender.tag == 1 {
self.timeNumberSelected == 5
} else if sender.tag == 2 {
self.timeNumberSelected == 10
} else {
self.timeNumberSelected == 24
}
sender.selected = true
self.timeNumberLabel.text = "\(self.timeNumberSelected)"
self.timeNumberLabel.setNeedsDisplay()
println(self.timeNumberLabel.text)
}
}
The label is clearly visible as shown in this picture. I didn't think it would be this hard to implement, but I was very wrong. I'm willing to bet it's something really simple that I'm missing.
Try adding the line
self.timeStringLabel.setNeedsDisplay()
(after the label change)
Because the code is run asynchronously, the interface-updating methods may miss the change and not display it (especially if a time-consuming bit of code occurs before the label change). Adding this code forces the interface to check for and display any changes it may have missed.
This should be used after any asynchronous task that changes the interface, as the task's running may overlap with the interface methods, resulting in a missed change.
*Thanks to the iOS Development Journal

Resources