iOS label does not update text even from main thread - ios

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.

Related

How can I determine the index path for the currently focused UITableViewCell using Voice Over?

I have a dynamic UITableView. For each cell, I add a UIAccessibilityCustomAction. When the action fires, I need to know the index path so I can respond accordingly and update my model.
In tableView(_:cellForRowAt:) I add my UIAccessibilityCustomAction like this...
cell.accessibilityCustomActions = [
UIAccessibilityCustomAction(
name: "Really Bad Name",
target: self,
selector: #selector(doSomething)
)
]
I have tried to use UIAccessibility.focusedElement to no avail...
#objc private func doSomething() {
let focusedCell = UIAccessibility.focusedElement(using: UIAccessibility.AssistiveTechnologyIdentifier.notificationVoiceOver) as! UITableViewCell
// Do something with the cell, like find the indexPath.
}
The problem is that casting to a cell fails. The debugger says that the return value type is actually a UITableTextAccessibilityElement, which I could find no information on.
When the action fires, I need to know the index path so I can respond accordingly and update my model.
The best way to reach your goal is to use the UIAccessibilityFocus informal protocol methods by overriding them in your object directly (the table view cell class in your case): you'll be able to catch the needed index path when a custom action is fired.
I suggest to take a look at this answer dealing with catching accessibility focus changed that contains a detailed solution with code snippets if need be.šŸ˜‰
Example snippet...
class SomeCell: UITableViewCell
override open func accessibilityElementDidBecomeFocused() {
// Notify view controller however you want (delegation, closure, etc.)
}
}
I ended up having to solve this myself to bodge an Apple bug. You've likely solved this problem, but this is an option similar to your first suggestion.
func accessibilityCurrentlySelectedIndexPath() -> IndexPath? {
let focusedElement:Any
if let voiceOverObject = UIAccessibility.focusedElement(using: UIAccessibility.AssistiveTechnologyIdentifier.notificationVoiceOver) {
focusedElement = voiceOverObject
} else if let switchControlObject = UIAccessibility.focusedElement(using: UIAccessibility.AssistiveTechnologyIdentifier.notificationSwitchControl) {
focusedElement = switchControlObject
} else {
return nil
}
let accessibilityScreenFrame:CGRect
if let view = focusedElement as? UIView {
accessibilityScreenFrame = view.accessibilityFrame
} else if let accessibilityElement = focusedElement as? UIAccessibilityElement {
accessibilityScreenFrame = accessibilityElement.accessibilityFrame
} else {
return nil
}
let tableViewPoint = UIApplication.shared.keyWindow!.convert(accessibilityScreenFrame.origin, to: tableView)
return tableView.indexPathForRow(at: tableViewPoint)
}
What we're essentially doing here is getting the focused rect (in screen coordinates) and then translating it back to the table view's coordinate space. We can then ask the table view for the indexpath which contains that point. Simple and sweet, though if you're using multi-window you may need to swap UIApplication.shared.keyWindow! with something more appropriate. Note that we deal with the issue you faced where the element was a UITableTextAccessibilityElement when we handle UIAccessibilityElement since UITableTextAccessibilityElement is a private, internal Apple class.

Pushing Firebase events To a Table View

I am trying to take some events from firebase and add them to a table view for the user to see. My current code looks like this:
For some reason, the events are only showing up when I print them in the closure (ref.child("Events") part), but outside, it is only showing the "hello" I pushed. Please help me fix this issue, preferably with actual code.
You need to reload after the for loop
self.tblEvents.reloadData()
Also put these 2 lines at the beginning of viewDidLoad
self.tblEvents.dataSource = self
self.tblEvents.delegate = self
Please reload your evens table view in view did load after loop like I have done in below code,
ref.child(ā€œEventsā€).observe(.value, with: { (data) in
let events = data.value as! [String:[String:Any]]
For (_, value) in events {
self.eventsArray.append(value[ā€œEventTitleā€]! as! String)
})
// add this line in your code
tblEvents.reloadData()

attach Firebase listeners to tableViewCell

I have a chat feature in an iOS app. the chat previews are presented in a tableView and I am using a class for the TableView cells themselves. I want to listen for new messages so I can put a "new Message" label on the cells. The relevant parts of the tableView Cell are:
var chat: Chat! {
didSet {
self.updateUI()
self.observeNewMessages()
}
}
extension ChatTableViewCell {
func observeNewMessages() {
let chatMessageIdsRef = chat.ref.child("messageIds")
chatMessageIdsRef.observe(.childAdded, with: { snapshot in
let messageId = snapshot.value as! String
DatabaseReference.messages.reference().child(messageId).observe(.childAdded, with: { snapshot in
//let message = Message(dictionary: snapshot.value as! [String : Any])
print("looks like there is a new message") //works
self.messageLabel.text = "New Message!"
//self.delegate?.newMessage()
})
})
}
}
The print statement is there to test the listener. The problem I am having is when the table view loads and the cells get created, observeNewMessages() is executing the print statement regardless of whether there were new messages. It is just reading the old children that are already there first? Obviously this is going to cause the message label to always read "New Message." I'm not too good with Firebase so is there a better Firebase function to use or a better way to do this?
*I am using 2 queries because the Message dictionary will allow me to see if the current user made the message or not. The delegate is there because my next step will be to reload the tableview -- as it is now, this will also cause an infinite print statement loop...

Preserving Images In an IBOutletCollection

Here is my code so far:
#IBOutlet var Boxes: [UIImageView]!
#IBAction func Success(_ sender: Any) {
if "" == defaults.value(forKey: "frequency") as? String{
for box in Boxes{
if box.image == UIImage(named:"blank.png"){
box.image = UIImage(named:"Arm.png")
break
}
}
}
else if "Twice" == defaults.value(forKey: "frequency") as? String{
for box in Boxes{
if box.image == UIImage(named:"Arm1.png"){
box.image = UIImage(named:"Arm.png")
break
}
else if box.image == UIImage(named:"blank.png"){
box.image = UIImage(named:"Arm1.png")
break
}
}
}
}
The defaults.value(forKey: frequency) are used to determine how often the user wants to change the grid. How would I create UserDefaults that would restore the images in the grid on app close? (double tap home and swipe up). Are UserDefaults even the right way to do this? I feel like it would be easier, but way messier to create an individual outlet for each UIImageView and saving the state for each one through a giant tree of code. Thank you!
If your images are stored in the bundle (manually added by you and won't be changed) you can save names to the userdefaults instead of saving images.
Otherwise I would use a database. There is very nice framework Realm. Try it. Also, to store images, it is better to convert them to the Data
I hacked my way through this.
I ended up looping through the list, and if my conditions were not satisfied, I added one to a variable (starting with 1) to index the list. If I changed the condition, I took the variable and added it to the end of the userDefault. Add a comment if you need any help.

UILabel text setting causes EXC_BAD_INSTR EXC_i386_INVOP

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

Resources