Swift - Updating UILabel Prevents Subviews from Changing - ios

I'm currently learning Swift with Stanford's iOS 11 course. One of the projects is a card game called "Set". For that project I have a subview called "cardView" inside the ViewController, where all of the cards on the field are displayed.
Inside that "cardView" I create a subview for each card.
The goal is to find 3 cards that make up a "set".
I also have a hint button, which shows me a possible solution by changing the color of 3 cards that make up a "set".
Everytime I tap on that hint button though my score will decrease.
This is my IBAction:
#IBAction private func hintButtonPressed(_ sender: UIButton) {
for subview in cardView.subviews {
subview.layer.borderWidth = 0
}
game.hint()
if !game.selectedCards.isEmpty {
for index in game.indicesOfSelectedCards {
cardView.subviews[index].layer.borderWidth = 4
cardView.subviews[index].layer.cornerRadius = 8
cardView.subviews[index].layer.borderColor = UIColor.red.cgColor
print("cardView.subview[\(index)] changed borderColor.")
}
game.resetSelectedCards()
} else {
print("There are no sets on the field")
}
scoreLabel.text = "Score: \(game.score)"
}
For some reason if I change the text of the scoreLabel the subviews/cards that make up a "set" won't update and I can't see any change of the border. If I delete this line
scoreLabel.text = "Score: \(game.score)" though I can see the change of the subviews but then I don't see my updated score...
I also worked with breakpoints to figure out on which line this bug occurs and I found out that my subviews won't change as long as I have that line in there where I change the text of the scoreLabel even though that line is at the end of that method.
I just can't figure out how to update the score + show a possible solution at the same time.
Can anyone help me?

Related

UILabel fails to update properly

I am a beginner and working on an iOS app in Xcode (Swift). For some reason I can set the text in a UILabel just fine using its property .text but if I try and update it the label will only show the last value I set (??). For example, here is the code that gets executed when I click a button called myButton and try to set the text in the label called Display. The result is that only the fourth value ("Four") is displayed (after the three 2 second delays) as the UILabel stays blank until that point. The really odd thing is that if I remove everything but the first line (Display.text = "One") it will display it just fine. It is as if the compiler removes all but the last property setting for the label... Any help is appreciated!!
#IBAction func myButton(_ sender: Any)
{
Display.text = "One"
sleep(2)
Display.text = "Two"
sleep(2)
Display.text = "Three"
sleep(2)
Display.text = "Four"
}
Update: This code is just an example that demonstrates a problem I encountered in my real program. The sleep() call is to simulate a delay only. The problem is that I can't seem to set the UILabel's text twice. In my code, I call a function that takes time to execute so I want to display "Wait..." in the label until it is done and then I want to display the result in the same label. But no luck...
Update 2: I found this in Apples documentation: "You provide the content for a label by assigning either a NSString object to the text property, or an NSAttributedString object to the attributedText property. The label displays the most recently set of these properties." Most recent? That seems to be my problem.How can I force it to update on-demand???
Don't use sleep it blocks the main thread
let arr = ["one","two","three","four"]
var counter = 0
Timer.scheduledTimer(withTimeInterval:2.0, repeats:true) { timer in
self.counter += 1
if counter < self.arr.count {
self.display.text = self.arr[counter]
}
else {
timer.invalidate()
}
}
Or use DispatchQueue.main.asyncAfter
arr.indices.forEach {
DispatchQueue.main.asyncAfter(deadline: .now() + Double( $0 * 2) ) {
self.display.text = self.arr[$0]
}
}

Detecting which ImageView has has been tapped

So i have 4 vStacks, each containing 9 ImageViews. Each ImageView represents one Card, alpha 0 is default. When a Card is Detected (with ARKit), my code sets the ImageView to alpha 1 so the user can see that the card has been scanned.
Now: I want to implement that when the user clicks on one of the ImageViews, an alert should pop up asking the user if he is sure he wants to delete the scanned card. My problem is, I have no idea what the best practice is to get the information that the card has been tapped and how to delete it without hardcoding.
In ViewDidLoad i set the images into the ImageVies like This:
//This repeats for all 36 ImageViews
imgView1.image = UIImage(named: "Eichel_6")
imgView2.image = UIImage(named: "Eichel_7")
/*When a image is detected with ARKit, this is what happens. Basically
*it pushes the corresponding reference name to an array called
* scannedCards, handles them, and removes them afterwards.
* spielPoints = gamepoints/points, spielmodus = gamemode
*/
func updateLabel() {
//print all cards in scanned cards
for card in scannedCards {
points += (DataController.shared.spielpoints[DataController.shared.spielmodus!]![card]! * DataController.shared.multCalculator[DataController.shared.spielmodus!]!)
}
scannedCards.removeAll()
}
I am a new to coding, I would be grateful if you correct me if my code snippets are bad, beside my question. Thank you in advance.
As has already been mentioned in comments, you should use a UICollectionView for this kind of work. #Fogmeister has promised to add an answer concerning that later, so I won't do that. But I can answer the actual question, even though it's not what you should do.
From your code I can see that you probably have outlets for all your imageViews (imgView1 ... imgView36) and set each image manually. To detect taps on any of these, you could do something like this:
func viewDidLoad(){
super.viewDidLoad()
let allImageViews = [imageView1, imageView2, .... imageView36]
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(gesture:)))
allImageViews.forEach({$0.addGestureRecognizer(tapGestureRecognizer)})
}
#objc func didTapImageView(gesture:UITapGestureRecognizer){
guard let imageView = gesture.view as? UIImageView else { return }
//Here you can put code that will happen regardless of which imageView was tapped.
imageView.alpha = 0.0
//If you need to know exactly which imageView was tapped, you can just check
if imageView == self.imageView1{
//Do stuff only for imageView1
}else if imageView == self.imageView2{
//...
}//....
}
Again, this is not very good practice. If you go for UICollectionView instead, you don't have to have outlets for all your imageViews and you don't have to create a gestureRecognizer for handling events. But still, I hope this helped you understand general gestures better.

UIPageControl voiceover issue

I have a UIPageControl in my app's onboarding process. Its purpose is not to change pages manually but as an indication of the user's process through the whole onboarding. (There's no swiping gestures right now)
Everything looks fine, but VoiceOver lets the user increment or decrement the control, and says it can be changed (it seems to keep .adjustable as a trait). I don't want that behaviour. I just want VoiceOver to read "Page 1 of 3". I disabled it, changed its accessibilityTraits and it doesn't affect VoiceOver.
Here is some code.
/// hard coded values for the example:
pageControl.numberOfPages = 3
pageControl.currentPage = 1
pageControl.isEnabled = false
pageControl.isUserInteractionEnabled = false
pageControl.accessibilityTraits = .none
I have created a test project on github for a more complete example.
One way to get your purpose is to subclass UIpageControl and override the accessibiliTraits property as follows:
class MyPageControl: UIPageControl {
override var accessibilityTraits: UIAccessibilityTraits {
get{
return .none
}
set{}
}
}
Define your pageControl element as MyPageControl to get the desired result.

Pressing one UIButton triggers another UIButton: I don't want this to happen

I am new to swift and xcode and am having a little trouble with creating this simple timer app with laps. When I press on the 'lap' button, the 'reset button is also triggered. Can anyone please help me with this? The buttons are right next to each other but I don't think this has anything to do with it. Additionally, I also commented out the entire body of the lap function and the same thing happened.
#IBAction func Reset(_ sender: Any)
{
min = 0
sec = 0
mil = 0
timer.invalidate()
createLabel()
var i = 0
while i < list.count
{
list[i].removeFromSuperview()
i = i + 1
}
running = false
}
#IBAction func Lap(_ sender: UIButton)
{
let word = UILabel()
list.append(word)
word.font = UIFont.systemFont(ofSize: 50)
word.text = make()
word.sizeToFit()
word.frame.origin = CGPoint(x:187.5,y:700)
self.Scroll.addSubview(word)
word.center = CGPoint(x:187,y:last)
last = last + 50
Scroll.contentSize.height = Scroll.contentSize.height + 50
}
It sounds like you wired up two #IBActions to your Lap button. Control-click on your Lap button in the Storyboard and delete the connection to Reset() by clicking on the x next to the action.
I'm guessing you copied the Reset button to make your Lap button. If the #IBAction is connected when you copy a button, you get that connection as well with the new button. So if you add a connection to the new button, it doesn't replace the previous action but instead becomes a second action.
I think you might have hooked up both buttons with same IBAction function. Make a print statement inside both IBAction functions and see what happens. Or, delete both IBAction functions (and referencing outlets from the storyboard) and re-connect. Also, check in the connection inspector (top right in storyboard) to see if you have any dead outlets.
Start debugging and you'll find the solution. Start from the first point. Like adding buttons in the storyboard and then connecting them to View Controller. I think you have connected one button to multiple actions.

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