What am I doing wrong when updating my user's credits and storing it to userDefaults? - swift

My code below has a button, pressing which should increase the number of credits my user has by 3. I have used get/set property observers for my variable to be saved to userDefaults. While the credits label reflects the change (increases by 3), the number isn't saved to userDefaults, and consequently resets back to the base number when I kill and restart the app.
var livesLeft: Int {
get {
return UserDefaults.standard.integer(forKey: "livesLeftSaved")
}
set {
UserDefaults.standard.set(newValue, forKey: "livesLeftSaved")
}
}
lazy var livesLeftLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
livesLeft = 0
label.text = String(livesLeft)
label.font = UIFont(name: "GillSans-BoldItalic", size: 25)
label.textAlignment = .center
label.numberOfLines = 2
return label
}()
let addLivesButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: "icon"), for: .normal)
button.addTarget(self, action: #selector(pressedButton), for: .touchUpInside)
return button
}()
#objc func pressedButton() {
livesLeft += 3
//I think the mistake I'm making is in the next line of code//
livesLeftLabel.text = String(livesLeft)
}
While the label gets updated from 0 to 3 on pressing the button, it fails to save the increased number to userDefaults and consequently the user loses his credits once I kill the app.
I know I'm missing out on something very trivial here, I can't figure out what I'm doing wrong

Your issue is here:
lazy var livesLeftLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
livesLeft = 0 *** HERE ***
You are resetting the livesLeft value when you load the label.
Just change this line to
livesLeftLabel.text = String(livesLeft)

Related

Button is not changing a value?

I wrote a code to create a Button which should change the value of a label/textfield.
I wanted to make a push-up App and every time you touch the button on the screen, the label '' Your Score: 0'' increase the score with 1, I have tried it as textfield too. But it's not working, nothing is happening!
Can someone help me?
Label Code:
func setupHelloWorld() {
helloworld.textAlignment = .center
helloworld.text = "Your Score: \(score)"
helloworld.textColor = .gray
helloworld.font = .boldSystemFont(ofSize: 30)
self.view.addSubview(helloworld)
...
Button code:
func setUpNetButton() {
nextButton.backgroundColor = .blue
nextButton.setTitleColor(.white, for: .normal)
nextButton.setTitle("Tap!", for: .normal)
nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside)
view.addSubview(nextButton)
setUpNextButtonConstraints()
}
#objc func nextButtonTapped() {
score += 1
}
You have to manually update the text property of the label. Just because you initially set its text using the score variable, it will not automatically react to any changes of the variable's value afterward unless you explicitly set the new label text.
Change your code to something like this and it will work:
func setupHelloWorld() {
helloworld.textAlignment = .center
helloworld.textColor = .gray
helloworld.font = .boldSystemFont(ofSize: 30)
updateButtonText()
self.view.addSubview(helloworld)
}
...
#objc func nextButtonTapped() {
score += 1
updateButtonText()
}
func updateButtonText() {
helloworld.text = "Your Score: \(score)"
}
Alternatively, instead of calling the updateButtonText from the nextButtonTapped() method, you can add a didSet observer to your score property and change the label text each time it gets assigned a new value. However, you'll still need to update the label's text when you view has loaded because didSet won't be called during the initialization of the class. Something like this:
private var score: Int = 0 {
didSet {
updateButtonText()
}
}
override func viewDidLoad() {
...
updateButtonText()
...
It is because you are not changing the value of helloworld assign the value to
helloworld.text in #objc function nextButtonTapped() after the score is updated

Why doesn't my UILabel in a nested view receive touch events / How can I test the Responder Chain?

I have found lots of similar questions about not receiving touch events and I understand that in some cases, writing a custom hitTest function may be required - but I also read that the responder chain will traverse views and viewControllers that are in the hierarchy - and I don't understand why a custom hitTest would be required for my implementation.
I'm looking for an explanation and/or a link to a document that explains how to test the responder chain. This problem is occurring in Xcode 10.2.1.
My scenario (I am not using Storyboard):
I have a mainViewController, that provides a full screen view with an ImageView and a few Labels. I have attached TapGestureRecognizers to the ImageView and one of the labels - and they both work properly.
When I tap the label, I add a child viewController and it's view as a subview to the mainViewController. The view is constrained to cover only the right-half of the screen.
The child viewController contains a vertical stack view that contains 3 arrangedSubviews.
Each arrangedSubview contains a Label and a horizontal StackView.
The horizontal stackView's each contain a View with a Label as a subview.
The Label in the subview sets it's isUserInteractionEnabled flag to True and adds a TapGestureRecognizer.
These are the only objects in the child ViewController that have 'isUserInteractionEnabled' set.
The Label's are nested fairly deep, but since this is otherwise a direct parent/child hierarchy (as opposed to the 2 views belonging to a NavigationController), I would expect the Label's to be in the normal responder chain and function properly. Do the Stack View's change that behavior? Do I need to explicitly set the 'isUserInteractionEnabled' value to False on some of the views? Is there way I can add logging to the ResponderChain so I can see which views it checked and find out where it is being blocked?
After reading this StackOverflow post I tried adding my gesture recognizers in viewDidLayoutSubviews() instead of what's shown below - but they still do not receive tap events.
Thank you in advance to any who can offer advice or help.
Here is the code for the label that is not responding to my tap events and the tap event it should call:
func makeColorItem(colorName:String, bgColor:UIColor, fgColor:UIColor) -> UIView {
let colorNumber:Int = colorLabelDict.count
let colorView:UIView = {
let v = UIView()
v.tag = 700 + colorNumber
v.backgroundColor = .clear
v.contentMode = .center
return v
}()
self.view.addSubview(colorView)
let tapColorGR:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapColor))
let colorChoice: UILabel = {
let l = UILabel()
l.tag = 700 + colorNumber
l.isUserInteractionEnabled = true
l.addGestureRecognizer(tapColorGR)
l.text = colorName
l.textAlignment = .center
l.textColor = fgColor
l.backgroundColor = bgColor
l.font = UIFont.systemFont(ofSize: 24, weight: .bold)
l.layer.borderColor = fgColor.cgColor
l.layer.borderWidth = 1
l.layer.cornerRadius = 20
l.layer.masksToBounds = true
l.adjustsFontSizeToFitWidth = true
l.translatesAutoresizingMaskIntoConstraints = false
l.widthAnchor.constraint(equalToConstant: 100)
return l
}()
colorView.addSubview(colorChoice)
colorChoice.centerXAnchor.constraint(equalTo: colorView.centerXAnchor).isActive = true
colorChoice.centerYAnchor.constraint(equalTo: colorView.centerYAnchor).isActive = true
colorChoice.heightAnchor.constraint(equalToConstant: 50).isActive = true
colorChoice.widthAnchor.constraint(equalToConstant: 100).isActive = true
colorLabelDict[colorNumber] = colorChoice
return colorView
}
#objc func tapColor(sender:UITapGestureRecognizer) {
print("A Color was tapped...with tag:\(sender.view?.tag ?? -1)")
if let cn = sender.view?.tag {
colorNumber = cn
let v = colorLabelDict[cn]
if let l = (v?.subviews.first as? UILabel) {
print("The \(l.text) label was tapped.")
}
}
}
It looks like the main reason you're not getting a tap recognized is because you are adding a UILabel as a subview of a UIView, but you're not giving that UIView any constraints. So the view ends up with a width and height of Zero, and the label exists outside the bounds of the view.
Without seeing all of your code, it doesn't look like you need the extra view holding the label.
Take a look at this... it will add a vertical stack view to the main view - centered X and Y - and add "colorChoice" labels to the stack view:
class TestViewController: UIViewController {
let stack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 4
return v
}()
var colorLabelDict: [Int: UIView] = [:]
override func viewDidLoad() {
super.viewDidLoad()
let v1 = makeColorLabel(colorName: "red", bgColor: .red, fgColor: .white)
let v2 = makeColorLabel(colorName: "green", bgColor: .green, fgColor: .black)
let v3 = makeColorLabel(colorName: "blue", bgColor: .blue, fgColor: .white)
[v1, v2, v3].forEach {
stack.addArrangedSubview($0)
}
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
func makeColorLabel(colorName:String, bgColor:UIColor, fgColor:UIColor) -> UILabel {
let colorNumber:Int = colorLabelDict.count
// create tap gesture recognizer
let tapColorGR:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapColor))
let colorChoice: UILabel = {
let l = UILabel()
l.tag = 700 + colorNumber
l.addGestureRecognizer(tapColorGR)
l.text = colorName
l.textAlignment = .center
l.textColor = fgColor
l.backgroundColor = bgColor
l.font = UIFont.systemFont(ofSize: 24, weight: .bold)
l.layer.borderColor = fgColor.cgColor
l.layer.borderWidth = 1
l.layer.cornerRadius = 20
l.layer.masksToBounds = true
l.adjustsFontSizeToFitWidth = true
l.translatesAutoresizingMaskIntoConstraints = false
// default .isUserInteractionEnabled for UILabel is false, so enable it
l.isUserInteractionEnabled = true
return l
}()
NSLayoutConstraint.activate([
// label height: 50, width: 100
colorChoice.heightAnchor.constraint(equalToConstant: 50),
colorChoice.widthAnchor.constraint(equalToConstant: 100),
])
// assign reference to this label in colorLabelDict dictionary
colorLabelDict[colorNumber] = colorChoice
// return newly created label
return colorChoice
}
#objc func tapColor(sender:UITapGestureRecognizer) {
print("A Color was tapped...with tag:\(sender.view?.tag ?? -1)")
// unwrap the view that was tapped, make sure it's a UILabel
guard let tappedView = sender.view as? UILabel else {
return
}
let cn = tappedView.tag
let colorNumber = cn
print("The \(tappedView.text ?? "No text") label was tapped.")
}
}
Result of running that:
Those are 3 UILabels, and tapping each will trigger the tapColor() func, printing this to the debug console:
A Color was tapped...with tag:700
The red label was tapped.
A Color was tapped...with tag:701
The green label was tapped.
A Color was tapped...with tag:702
The blue label was tapped.

Setting a variable to a defined lazy var within a UIView closure cause reference problems

Recently, I was working on a project of mine and I wanted to have multiple labels with the same font, text color, and properties, except their text.
This is the code I wrote:
lazy var profileLabel: UILabel = {
let label = UILabel()
label.font = .displayNameLabel
label.textColor = .profileLabel
label.numberOfLines = .numberOfLines
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
lazy var displayName: UILabel = {
let label = profileLabel
label.text = "Kevin"
return label
}()
lazy var countryLabel: UILabel = {
let label = profileLabel
label.text = "US"
return label
}()
As you can see, to remedy my issue, I created a single label that had all the properties I wanted for all my other labels. For my other labels, I thought I was creating a new label by typing let label = profileLabel. But as it turns out, I wasn't. After consecutive calls of setting the text and adding the labels to my view, only 1 label was actually displayed, and it was the last label added; so in this case, it would be the countryLabel.
It seems to me that in all my calls to let label = profileLabel, I'm just creating a reference to the same profileLabel. And if this is the case, would changing lazy var profileLabel to var profileLabel fix this issue and create a new label with the needed properties every time profileLabel is called?
You were intended to use computed property of swift. But didn’t get it right. Your profile label should have been defined as follows.
var profileLabel: UILabel {
get {
let label = UILabel()
label.font = .displayNameLabel
label.textColor = .profileLabel
label.numberOfLines = .numberOfLines
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}
}

How to setup constraints with SnapKit for UITextField and UILabel?

I have a [[TextField need max available width----]offset(10.0)][Label]]. I want to setup TextFeild pin to left and shrink all available space without label trimming and setup label to pin right and get minimal fit size.
lazy var textField: UITextField = {
var textField = UITextField()
textField.placeholder = "Placeholder"
textField.delegate = self
textField.borderStyle = UITextField.BorderStyle.none
textField.keyboardType = UIKeyboardType.numberPad
textField.returnKeyType = UIReturnKeyType.done
textField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return textField
}()
lazy var measureLabel: UILabel = {
var label = UILabel()
label.numberOfLines = 1
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return label
}()
measureLabel.snp.makeConstraints { (make) in
make.right.equalTo(self.snp.right)
make.centerY.equalToSuperview()
}
textField.snp.makeConstraints { (make) in
make.left.equalToSuperview()
make.right.equalTo(self.measureLabel.snp.left).offset(-10.0)
make.centerY.equalToSuperview()
}
You need
label.setContentHuggingPriority(.required, for: .horizontal)
label.setContentCompressionResistancePriority(.required, for: .horizontal)
Also you can completely remove these 2 lines as by default textfield's ContentHuggingPriority && ContentCompressionResistancePriority is lower than the default for label , plus the textfield has no intrinsic size
Implement the a demo below.
label can automatically increase height with attributes below. (swift 5)
label = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
It can make the height of super view synchronize at the same time, when you set
the height of super view the same as label.
label = UILabel()
viewContainer.addSubview(label)
label.backgroundColor = UIColor.white
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.text = "hello world, today is a new day. Have a nice day. hello world, today is a new day. Have a nice day. hello world, today is a new day. Have a nice day. hello world, today is a new day. Have a nice day. hello world, today is a new day. Have a nice day. hello world, today is a new day. Have a nice day."
self.addSubview(label)
label.snp.makeConstraints { (make) in
let superView = viewContainer!
make.left.equalTo(superView).offset(10)
make.right.equalTo(superView).offset(-10)
make.centerY.equalTo(superView)
}
viewContainer.snp.makeConstraints { (make) in
make.centerY.equalTo(self)
make.centerX.equalTo(self)
make.left.equalTo(self).offset(10)
make.right.equalTo(self).offset(-10)
make.height.equalTo(label).offset(100)
}
download code: https://github.com/zgpeace/SnapkitDemo/tree/dynamicHeightLabel

Will this statement always evaluate to nil in Swift?

open var buttonInit: ((_ index: Int) -> UIButton?)?
...
if let button: UIButton = self.buttonInit?(i) {
finButton = button
}else {
let button = UIButton(type: .custom)
button.setTitleColor(button.tintColor, for: [])
button.layer.borderColor = button.tintColor.cgColor
button.layer.borderWidth = 1
button.layer.cornerRadius = buttonHeight/2
finButton = button
}
I don't find any function description about buttonInit in AZDialogViewController. Does it mean button: UIButton = self.buttonInit?(i) will always be nil and finButton = button will not be executed?
The latter part of the code you quoted is in the setUpButton method:
fileprivate func setupButton(index i:Int) -> UIButton{
if buttonHeight == 0 {buttonHeight = CGFloat(Int(deviceHeight * 0.07))}
let finButton: UIButton
if let button: UIButton = self.buttonInit?(i) {
finButton = button
}else {
let button = UIButton(type: .custom)
button.setTitleColor(button.tintColor, for: [])
button.layer.borderColor = button.tintColor.cgColor
button.layer.borderWidth = 1
button.layer.cornerRadius = buttonHeight/2
finButton = button
}
This method is called here:
open func addAction(_ action: AZDialogAction){
actions.append(action)
if buttonsStackView != nil{
let button = setupButton(index: actions.count-1)
self.buttonsStackView.addArrangedSubview(button)
//button.frame = buttonsStackView.bounds
button.center = CGPoint(x: buttonsStackView.bounds.midX,y: buttonsStackView.bounds.maxY)
animateStackView()
}
}
From this we can see that buttonInit seems to be used to let the user of the library specify what kind of button they want as the action buttons. Another piece of evidence is that buttonInit is declared open, so it is likely that it is the client code who should set this, not the AZDialogViewController.
Plus, the README file showed this usage:
Use custom UIButton sub-class:
dialog.buttonInit = { index in
//set a custom button only for the first index
return index == 0 ? HighlightableButton() : nil
}
So to answer your question, the if branch will be executed if you set buttonInit.
#Huwell,
the documentation in the repository states to initialize the button in the following manner:
dialog.buttonInit = { index in
//set a custom button only for the first index
return index == 0 ? HighlightableButton() : nil
}
The button should be part of your DialogViewController.