Swift UIKit label text doesn't update / view doesn't update - ios

I have a problem:
I have a list of items this is controller A, and when I click on any item I go to controller B (item info), I then execute the ledLightingButton_Tapped function by pressing the corresponding button that activates the LED indicator for the animal.
#IBAction func ledLightingButton_Tapped(_ sender: Any) {
if !GlobalData.shared.led_animals.contains(GlobalData.shared.selectedAnimalId) {
GlobalData.shared.led_animals.append(GlobalData.shared.selectedAnimalId)
}
activateLED(at: GlobalData.shared.selectedAnimalId)
}
func activateLED(at animalId: String) {
ServerSocket.shared?.perform(
op: "ActivateLED",
with: [
"light_duration": "180",
"led_color": "White",
"client_data": "",
"led_animals": [animalId]
]
) { err, data in
guard err == nil else { return }
print(data)
let ledStatus = data[0]["led_request_status"].stringValue
self.ledStatusLabel.text = ledStatus
GlobalData.shared.isActiveLED = true
self.startTimer()
}
}
Upon successful activation, the animal number is added to the array, and the startTimer is called which every 10 seconds requests checkLEDStatus for all animals in the array.
func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(updateCowStatus), userInfo: nil, repeats: true)
}
#objc func updateCowStatus() {
self.checkLEDStatus()
}
func checkLEDStatus() {
ServerSocket.shared?.perform(
op: "CheckStatusLED",
with: [
"light_duration": "180",
"led_color": "White",
"client_data": "",
"led_animals": GlobalData.shared.led_animals
]
) { err, data in
guard err == nil else {
GlobalData.shared.isActiveLED = false
self.stopTimer()
return
}
DispatchQueue.global(qos: .background).async {
for i in 0..<data.count {
if GlobalData.shared.selectedAnimalId == data[i]["animal_id"].stringValue {
let ledStatus = data[i]["led_request_status"].stringValue
if ledStatus.contains("Fail") {
guard let index = GlobalData.shared.led_animals.firstIndex(of: GlobalData.shared.selectedAnimalId) else { return }
GlobalData.shared.led_animals.remove(at: index)
}
DispatchQueue.main.async {
self.ledStatusLabel.text = ledStatus
}
}
}
}
}
}
The current status of the animal is displayed on the label. If you go in the controller A and activate the status + get a result from checkedLEDstatus - it is work for one animal - everything works, but if you go to controller B, activate for animal number 1, go out and open animal number 2 - perform activation, return to animal number 1 - then the label is no longer is updated, it does not display the new value, but I check it from debugging and property self.ledStatuslabel.text contains new value but UI didn't update. self.ledStatuslabel.text show old value.
Please help me, thanks!

Related

UITableView scrolling performance problem

I am currently working as a 5 month junior ios developer.
The project I'm working on is an application that shows the prices of 70 cryptocurrencies realtime with websocket connection.
we used websocket connection, UItableview, UITableViewDiffableDataSource, NSDiffableDataSourceSnapshot while developing the application.
But right now there are problems such as slowdown scrolling or not stop scroling and UI locking while scrolling in the tableview because too much data is processed at the same time.
after i check cpu performance with timer profiler I came to the conclusion that updateDataSource and updateUI functions exhausting the main thread.
func updateDataSource(model: [PairModel]) {
var snapshot = DiffableDataSourceSnapshot()
let diff = model.difference(from: snapshot.itemIdentifiers)
let currentIdentifiers = snapshot.itemIdentifiers
guard let newIdentifiers = currentIdentifiers.applying(diff) else {
return
}
snapshot.appendSections([.first])
snapshot.deleteItems(currentIdentifiers)
snapshot.appendItems(newIdentifiers)
dataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
}
func updateUI(data: SocketData) {
guard let newData = data.data else { return }
guard let current = data.data?.price else { return }
guard let closed = data.data?.lastDayClosePrice else { return }
let dailyChange = ((current - closed)/closed)*100
DispatchQueue.main.async { [self] in
if model.filter({ $0.symbol == newData.pairSymbol }).first != nil {
let index = model.enumerated().first(where: { $0.element.symbol == newData.pairSymbol})
guard let location = index?.offset else { return }
model[location].price = current
model[location].dailyPercent = dailyChange
if calculateLastSignalTime(alertDate: model[location].alertDate) > 0 {
//Do Nothing
} else {
model[location].alertDate = ""
model[location].alertType = ""
}
if let text = allSymbolsView.searchTextField.text {
if text != "" {
filteredModel = model.filter({ $0.name.contains(text) || $0.symbol.contains(text) })
updateDataSource(model: filteredModel)
} else {
filteredModel = model
updateDataSource(model: filteredModel)
}
}
}
delegate?.pricesChange(data: self.model)
}
}
Regards.
ALL of your code is running on the main thread. You have to wrap your entire updateUI function inside a DispatchQueue.global(qos:), and then wrap your dataSource.apply(snapshot) line inside a DispatchQueue.main.async. the dataSource.apply(snapshot) line is the only UI work you're doing in all that code you posted.

How do I return the next array when a button is pressed?

I'm new to programming and Swift and have been following along with a tutorial. I'm learning about the MVC design pattern and I have a function that changes the Title label and Button labels when a button is pressed. Once we get to Story 1 or Story 2 and we select any of the choices, I want it to restart to Story 0 and the original choices. However, when the story changes to Story 1, choice2 triggers Story 2 and when the story changes to Story 2, choice1 triggers Story1. I want it to reset to Story 0 when any choice is chosen. I tried to modify the nextStory function but have been unsuccessful. Any help would be greatly appreciated.!
Model:
struct StoryBrain {
var storyNumber = 0
let story = [Story(t: "You see a fork in the road.", c1: "Take a left.", c2: "Take a right."),
Story(t: "You see a tiger.", c1: "Shout for help.", c2: "Play dead."),
Story(t: "You find a treasure chest", c1: "Open it.", c2: "Check for traps.")
]
func getStoryTitle() -> String {
return story[storyNumber].title
}
func getChoice1() -> String {
return story[storyNumber].choice1
}
func getChoice2() -> String {
return story[storyNumber].choice2
}
mutating func nextStory(_ userChoice: String) {
if userChoice == story[storyNumber].choice1 {
storyNumber = 1
} else if userChoice == story[storyNumber].choice2 {
storyNumber = 2
}
}
}
View Controller
#IBAction func choiceMade(_ sender: UIButton) {
storyBrain.nextStory(sender.currentTitle!)
updateUI()
}
func updateUI() {
storyLabel.text = storyBrain.getStoryTitle()
choice1Button.setTitle(storyBrain.getChoice1(), for: .normal)
choice2Button.setTitle(storyBrain.getChoice2(), for: .normal)
}
If storyNumber is 0 then check choice for update next story.
If storyNumber is not 0 it might be 1 or 2 you should reset story to 0.
mutating func nextStory(_ userChoice: String) {
if storyNumber == 0 {
if userChoice == story[storyNumber].c1 {
storyNumber = 1
} else if userChoice == story[storyNumber].c2 {
storyNumber = 2
}
} else {
storyNumber = 0
}
}
I hope this help.
Hopefully, I am understanding you correctly. You just want to restart once you reach story 2.
mutating func nextStory(_ userChoice: String) {
guard storyNumber != 0 else { storyNumber = 0; return }
if userChoice == story[storyNumber].choice1 {
storyNumber = 1
return
} else if userChoice == story[storyNumber].choice2 {
storyNumber = 2
return
}
}
Hopefully, something like this works for you. Just off the top of my head.

textField Editing Changed not reacting fast enough (Asynchronous calls)

I have a textfield that queries a firebase database for existing users and then display a UIImage according to if the user is available or not. The problem is that once the async code loads, the textfield doesn't react on changed value.
example. If i type 12345 as a username, i don't query the database. Everything ok. If i add a 6 it queries firebase and it shows me the user is free. if i press backspace and have 12345 the textFieldChanged is triggered again, and database is not queried. All OK.
but the problem is, when i have 12345, and i type 6 and very fast back so i have 12345, the query is running and shows me the available icon (because the back was pressed very fast). Is this because of the Simulator or is it a real problem and can i be fixed easily ?
my code:
#IBAction func textFieldChanged(_ sender: UITextField) {
if let username = usernameInputText.text, username.count > 5 {
checkIfUserExists(username: username) { doesExist in //(2)
if doesExist! {
self.completeSignupButton.isEnabled = false
self.ifAvailableImageView.image = UIImage(named: "Close")
} else {
self.completeSignupButton.isEnabled = true
self.ifAvailableImageView.image = UIImage(named: "Check")
}
}
} else {
ifAvailableImageView.image = UIImage(named: "Close")
self.completeSignupButton.isEnabled = false
}
}
func checkIfUserExists(username: String, completion: #escaping (Bool?) -> Void) {
spinner.startAnimating()
self.ifAvailableImageView.image = nil
let docRef = db.collection("users").document(username)
docRef.getDocument { (document, error) in
if error != nil {
self.spinner.stopAnimating()
completion(nil)
} else {
self.spinner.stopAnimating()
if let document = document {
if document.exists {
completion(true)
} else {
completion(false)
}
}
}
}
}
You can just compare the username being processed with the current text in the text field and not process the result if it not the same because you only want to process the latest one.
#IBAction func textFieldChanged(_ sender: UITextField) {
if let username = usernameInputText.text, username.count > 5 {
checkIfUserExists(username: username) { doesExist in //(2)
// Check if current text and the completion being processed are for the same username
if username != sender.text {
return
}
if doesExist! {
self.completeSignupButton.isEnabled = false
self.ifAvailableImageView.image = UIImage(named: "Close")
} else {
self.completeSignupButton.isEnabled = true
self.ifAvailableImageView.image = UIImage(named: "Check")
}
}
} else {
ifAvailableImageView.image = UIImage(named: "Close")
self.completeSignupButton.isEnabled = false
}
}

Empty array causing my app to crash in Swift

I keep coming across this error "fatal error: Index out of range", after researching this I am still unsure of exactly how to fix this issue. To give context I have started off with an empty array var playersArray = [UITextField]() so that users can enter their names in order to play the game. I then make sure that the user has or has not entered a value into the text field for each name slot
if let player1Name = name1.text, !player1Name.isEmpty
{ playersArray.append(name1)
} else {
print("Player 1 Empty")
If the player has entered a value into that textfield then ill append that value to the array.
The issue I have is that if I run the game and no user has entered a name into any of the 10 textfields then the game will crash. Im assuming this is because the array is empty?
The error appears on this line where I randomize the names used for the game with the number of elements in the array:
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
I assume if the array is empty then .count will not work?
How can I make sure the game wont crash if the array is empty?
CODE:
var playersArray = [UITextField]()
override func viewDidLoad() {
super.viewDidLoad()
textColor()
question1View.isHidden = true
questionLabel.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Alert message on startup
func alertMessageOnStartUp(){
let alert = UIAlertController(title: "Warning!", message: "Please drink responsibly. By continuing, you agree that you are responsible for any consequences that may result from BottomsUp.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Agree", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
// Dismiss keyboard when tapped outside the keyboard
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
// Dimiss keybaord when return button is tapped
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
name1.resignFirstResponder()
name2.resignFirstResponder()
name3.resignFirstResponder()
name4.resignFirstResponder()
name5.resignFirstResponder()
name6.resignFirstResponder()
name7.resignFirstResponder()
name8.resignFirstResponder()
name9.resignFirstResponder()
name10.resignFirstResponder()
return(true)
}
//randomise background colour of each question page
func getRandomBackgroundColor() -> UIColor{
let randomRed:CGFloat = CGFloat(drand48())
let randomGreen:CGFloat = CGFloat(drand48())
let randomBlue:CGFloat = CGFloat(drand48())
return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
}
func textColor(){
name1.textColor = UIColor.white
name2.textColor = UIColor.white
name3.textColor = UIColor.white
name4.textColor = UIColor.white
name5.textColor = UIColor.white
name6.textColor = UIColor.white
name7.textColor = UIColor.white
name8.textColor = UIColor.white
name9.textColor = UIColor.white
name10.textColor = UIColor.white
}
#IBAction func playButton(_ sender: Any) {
alertMessageOnStartUp()
if let player1Name = name1.text, !player1Name.isEmpty
{ playersArray.append(name1)
} else {
print("Player 1 Empty")
}
if let player2Name = name2.text, !player2Name.isEmpty
{ playersArray.append(name2)
} else {
print("Player 2 Empty")
}
if let player3Name = name3.text, !player3Name.isEmpty
{ playersArray.append(name3)
} else {
print("Player 3 Empty")
}
if let player4Name = name4.text, !player4Name.isEmpty
{ playersArray.append(name4)
} else {
print("Player 4 Empty")
}
if let player5Name = name5.text, !player5Name.isEmpty
{ playersArray.append(name5)
} else {
print("Player 5 Empty")
}
if let player6Name = name6.text, !player6Name.isEmpty
{ playersArray.append(name6)
} else {
print("Player 6 Empty")
}
if let player7Name = name7.text, !player7Name.isEmpty
{ playersArray.append(name7)
} else {
print("Player 7 Empty")
}
if let player8Name = name8.text, !player8Name.isEmpty
{ playersArray.append(name8)
} else {
print("Player 8 Empty")
}
if let player9Name = name9.text, !player9Name.isEmpty
{ playersArray.append(name9)
} else {
print("Player 9 Empty")
}
if let player10Name = name10.text, !player10Name.isEmpty
{ playersArray.append(name10)
} else {
print("Player 10 Empty")
}
question1View.isHidden = false
question1View.backgroundColor = getRandomBackgroundColor()
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
#IBAction func nextQuestionButton(_ sender: Any) {
question1View.backgroundColor = getRandomBackgroundColor()
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
}
Breaking this down:
Int(arc4random_uniform(UInt32(playersArray.count)))
This line gets a random number with a minimum value of 0 and a maximum value of the length of the playersArray minus 1.
I'm actually not sure what it does when the argument you pass in is 0, but it doesn't really matter, as we'll see next.
Then you use that random value here:
playersArray[thatRandomNumber]
Because there are no elements in playersArray, no matter what the value is of thatRandomNumber, it's going to be out of bounds.
You probably want something more like this:
let RandomPlayer = <some default value>
if !playersArray.isEmpty {
RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
}
EDIT
Your latest code still doesn't seem to do anything to prevent indexing into the empty array.
You have:
#IBAction func playButton(_ sender: Any) {
...
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
You need:
#IBAction func playButton(_ sender: Any) {
...
if playersArray.isEmpty {
// do something about that
} else {
let RandomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
let RandomQuestion = questionArray[Int(arc4random_uniform(UInt32(questionArray.count)))]
questionLabel.text = RandomPlayer.text! + RandomQuestion
}
}
playersArray.count for an empty array is 0, so you are trying to access playersArray[0] - but the array is empty, so nothing exists at the 0 index.
You should do something like this:
let randomPlayer: Player
if !playersArray.isEmpty {
randomPlayer = playersArray[Int(arc4random_uniform(UInt32(playersArray.count)))]
} else {
randomPlayer = Player() //create a fallback player
}
Alternatively you could make randomPlayer an optional, rather than providing a fallback value. Depends on your needs for that variable.

GCD Cancel and restart queue when button pressed again

Simply, I have a button that adds to a queue, or I believe that's how it works - I've only just started to use GCD. If I press the button twice in my app, it runs these two processes in parallel and the progress bar races back and forth between them, I'd like to stop all the other instances of the queue and execute a new one if that makes sense, I've not got to grips with the lingo...
#IBAction func makePi(_ sender: UIButton) {
piProgress.progress = 0.0
let queue1 = DispatchQueue(label: "com.appcoda.queue1", qos: DispatchQoS.userInitiated)
queue1.async {
let currentAmount = Int(self.randNum.value)
var gcdCounter = 0
for n in 1...currentAmount {
if self.gcd(self.random(max: Int(self.randRange.value)), self.random(max: Int(self.randRange.value))) == 1{
gcdCounter += 1
}
if n % 1000 == 0 {
DispatchQueue.main.async {
self.piProgress.setProgress(Float(Double(n)/Double(currentAmount)), animated: true)
}
} else if n == currentAmount {
DispatchQueue.main.async {
self.piProgress.setProgress(1.0, animated: true)
}
}
}
let piFinal = sqrt(Double((6/Double((Double(gcdCounter)/Double(currentAmount))))))
let roundedAccuracy: Double = ((piFinal-Double.pi)/Double.pi*100).roundTo(places: 6)
DispatchQueue.main.async {
self.piLabel.text = String(piFinal)
self.piAccuracy.text = "Accuracy: \(roundedAccuracy)%"
}
}
}
I just need all the other queue1.async ones to stop...
P.s. If I've done anything obviously wrong, please help ^^ Thanks.

Resources