Understanding where auto constraints are being set - ios

In the below autolayout challenge 6, from Ray W, I can't understand where in the code, the width and height change after it is tapped more than once.
It is set to 50 in IB, and on the first run through within the tappedImage method, in the last else condition, width.constant = 100, but when does this ever get redefined back to 50 (width.constant = 50)?!?
AND, after the first tap (i.e., width and height of this image = 100), tapping another image next, the width and height of the first image goes back to 50 while the second image grows to 100. Where in the last else condition does it tell the first image to resize to 50?
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var rayWidth: NSLayoutConstraint!
#IBOutlet weak var rayHeight: NSLayoutConstraint!
#IBOutlet weak var ray: UIImageView!
#IBOutlet weak var vickiWidth: NSLayoutConstraint!
#IBOutlet weak var vickiHeight: NSLayoutConstraint!
#IBOutlet weak var vicki: UIImageView!
#IBOutlet weak var gregWidth: NSLayoutConstraint!
#IBOutlet weak var gregHeight: NSLayoutConstraint!
#IBOutlet weak var greg: UIImageView!
#IBOutlet weak var micWidth: NSLayoutConstraint!
#IBOutlet weak var micHeight: NSLayoutConstraint!
#IBOutlet weak var mic: UIImageView!
#IBOutlet weak var christineWidth: NSLayoutConstraint!
#IBOutlet weak var christineHeight: NSLayoutConstraint!
#IBOutlet weak var christine: UIImageView!
#IBOutlet weak var name: UILabel!
#IBOutlet weak var bio: UITextView!
var heights:[NSLayoutConstraint]!
var widths:[NSLayoutConstraint]!
var bios: [String]!
var names: [String]!
#IBOutlet weak var emailButton: UIButton!
var previousHeight: NSLayoutConstraint?
var previousWidth: NSLayoutConstraint?
override func viewWillAppear(animated: Bool) {
bios = ["Ray is an indie software developer currently focusing on iPhone and iPad development, and the administrator of this site. He’s the founder of a small iPhone development studio called Razeware, and is passionate both about making apps and teaching others the techniques to make them.", "Vicki Wenderlich discovered a love of digital art in 2009, and has been making app art and digital illustrations ever since. She is passionate about helping people pursue their dreams, and makes free app art for developers available on her website, http://www.vickiwenderlich.com.", "Greg is an iOS developer and trainer, and has been on the raywenderlich.com editorial team since 2012. He has been nerding out with computers since the Commodore 64 era in the 80s and continues to this day on the web and on iOS. He likes caffeine, codes with two-space tabs, and writes with semicolons.", "Mic Pringle is a developer, editor, podcaster, and video tutorial maker. He's also Razeware's third full-time employee. When not knee-deep in Swift or stood in-front of his green screen, he enjoys spending time with his wife Lucy and their daughter Evie, as-well as attending the football matches of his beloved Fulham FC. You can find Mic on Twitter, GitHub, and Stack Overflow.", "Christine is Ray's administrative assistant. She tries to keep order in the ever expanding world of raywenderlich.com so that Ray and the team can stay focused on making more tutorials, books, and apps!"]
names = ["Ray Wenderlich", "Vicki Wenderlich", "Greg Heo", "Mic Pringle", "Christine Sweigart"]
name.text = ""
emailButton.hidden = true
}
override func viewDidLoad() {
super.viewDidLoad()
var razeware = [ray, vicki, greg, mic, christine]
heights = [rayHeight, vickiHeight, gregHeight,micHeight,christineHeight]
widths = [rayWidth, vickiWidth, gregWidth,micWidth,christineWidth]
for image in razeware {
var tapGesture = UITapGestureRecognizer(target: self, action: "tappedImage:")
image.userInteractionEnabled = true
image.addGestureRecognizer(tapGesture)
}
}
func tappedImage(sender:UITapGestureRecognizer!) {
var tag = 0
// index / tag who's been tapped
if let senderTag = sender.view?.tag {
tag = senderTag
}
let width = widths[tag] // get the width constraint from the array
let height = heights[tag]
print(width)
view.setNeedsUpdateConstraints()
if previousHeight == height {
if previousHeight?.constant == 100 {
previousHeight?.constant = 50
previousWidth?.constant = 50
print("through here")
//UIView.animateWithDuration(3.0, animations: { () -> Void in
self.name.text = ""
self.bio.text = ""
self.emailButton.hidden = true
// self.view.layoutIfNeeded()
// })
} else {
previousHeight?.constant = 100
previousWidth?.constant = 100
print("Going here")
name.text = names[tag]
bio.text = bios[tag]
bio.font = UIFont.systemFontOfSize(15.0)
bio.textColor = UIColor.whiteColor()
emailButton.hidden = false
}
} else {
previousHeight?.constant = 50
previousWidth?.constant = 50
UIView.animateWithDuration(5.0, animations: { () -> Void in
width.constant = 100
height.constant = 100
self.name.text = self.names[tag]
self.bio.text = self.bios[tag]
self.bio.font = UIFont.systemFontOfSize(15.0)
self.bio.textColor = UIColor.whiteColor()
self.emailButton.hidden = false
self.view.layoutIfNeeded()
})
}
print(width)
previousHeight = height
previousWidth = width
print("Previous: \(previousHeight)")
print("Height: \(height)")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Even if a constraint was re-defined in code elsewhere previously, layoutIfNeeded() will revert to the original IB defined constraint value - unless the constraint is re-defined again (to maintain its new value) after setNeedsUpdateContraints().
This is based on a lot of trial an even more error. Perhaps there is still a chance for a better answer...

Related

Cannot use instance member 'userCard1' within property initializer; property initializers run before 'self' is available error [duplicate]

This question already has answers here:
How to initialize properties that depend on each other
(4 answers)
Closed 2 years ago.
Trying to make a simple Blackjack app to get comfortable with using Xcode. I've never coded in Swift before and have a little experience in C++. Having an error I don't know how to fix. I'm having trouble applying other answers to similar questions to my situation.
I'm not totally sure what's causing the problem, let alone how to fix it :)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
var balance = 500
let suits = ["h","d","c","s"]
//user card declarations
#IBOutlet weak var userCard1: UIImageView!
#IBOutlet weak var userCard2: UIImageView!
#IBOutlet weak var userCard3: UIImageView!
#IBOutlet weak var userCard4: UIImageView!
#IBOutlet weak var userCard5: UIImageView!
#IBOutlet weak var userCard6: UIImageView!
#IBOutlet weak var userCard7: UIImageView!
#IBOutlet weak var userCard8: UIImageView!
#IBOutlet weak var userCard9: UIImageView!
#IBOutlet weak var userCard10: UIImageView!
//dealer card declarations
#IBOutlet weak var dealerCard1: UIImageView!
#IBOutlet weak var dealerCard2: UIImageView!
#IBOutlet weak var dealerCard3: UIImageView!
#IBOutlet weak var dealerCard4: UIImageView!
#IBOutlet weak var dealerCard5: UIImageView!
var win = false
var bet = 0
//bet label
#IBOutlet weak var betLabel: UILabel!
//bet slider
#IBAction func betSlider(_ sender: UISlider) {
betLabel.text = String(Int(sender.value))
}
#IBOutlet weak var betSliderValue: UISlider!
var userCardSlot = 3
var dealerCardSlot = 3
var userTotal = 0
var dealerTotal = 0
//user cards displayed
var userCardArray = [userCard1, userCard2, userCard3, userCard4, userCard5, userCard6, userCard7, userCard8, userCard9, userCard10]
//dealer cards displayed
var dealerCardArray = [dealerCard1, dealerCard2, dealerCard3, dealerCard4, dealerCard5]
//hit button
#IBAction func hitTapped(_ sender: Any) {
var cardNum = Int.random(in: 2...14)
var cardSuit = Int(arc4random()) % 4
userCardArray[userCardSlot].image = UIImage(named: "\(cardSuit)card\(cardNum)")
}
//stand button
#IBAction func standTapped(_ sender: Any) {
for userTotal in userCardArray.reversed() {
userTotal += cardNum
}
for dealerTotal in dealerCardArray {
dealerTotal += cardNum
}
while dealerTotal < 17 {
dealerCardSlot += 1
dealerCardArray[dealerCardSlot].image = UIImage(named: "\(cardSuit)card\(cardNum)")
}
if dealerTotal <= userTotal {
win = true
} else {
win = false
}
}
#IBAction func newRoundTapped(_ sender: Any) {
bet = Int(betSliderValue.value)
userCardArray[0].image = UIImage(named: "\(cardSuit)card\(cardNum)")
userCardArray[1].image = UIImage(named: "\(cardSuit)card\(cardNum)")
dealerCardArray[0].image = UIImage(named: "\(cardSuit)card\(cardNum)")
dealerCardArray[0].image = UIImage(named: "Red_back.jpg")
//prompt for hit or stand
if win == true {
balance += (bet * 2)
} else {
balance -= bet
}
}
}
Any help's appreciated!
You generally cannot use one property to initialize another outside of init, because when you do:
var b = a + 1
the compiler implicitly does:
var b = self.a + 1
but self is not yet available; it's only available during the init.
The only exception is a lazy property initializer:
lazy var b: Int = self.a + 1
In your case, you can move the initialization into init:
var userCardArray: [UIImageView]
init() {
self.userCardArray = [userCard1, userCard2, ... ]
}

Looking to accept user input, and calculate a running total - Swift 5/Xcode

I am a beginner working on an app that will function like a golf scorecard. My first issue has come while attempting to accept user input in a (prototype) series of 9 textFields so the user can type in their scores, and then a textView at the end that is not editable that will show the total for the 9 holes. I am trying to get this to be a running total that updates constantly.
I tried passing the inputs from each textField into an array, and returning the sum of the array to a the textView, but still had issues with the data type from the textField being a string, where as I will only be dealing with integers.
My clunky first pass is as follows -
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var holeOneScore: UITextField!
#IBOutlet weak var holeTwoScore: UITextField!
#IBOutlet weak var holeThreeScore: UITextField!
#IBOutlet weak var holeFourScore: UITextField!
#IBOutlet weak var holeFiveScore: UITextField!
#IBOutlet weak var holeSixScore: UITextField!
#IBOutlet weak var holeSevenScore: UITextField!
#IBOutlet weak var holeEightScore: UITextField!
#IBOutlet weak var holeNineScore: UITextField!
#IBOutlet weak var totalForFrontNine: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
//MARK: Calculate Scores
#IBAction func calculate(_ sender: Any) {
let hole1:Int = Int(holeOneScore.text!)!
let hole2:Int = Int(holeTwoScore.text!)!
let hole3:Int = Int(holeThreeScore.text!)!
let hole4:Int = Int(holeFourScore.text!)!
let hole5:Int = Int(holeFiveScore.text!)!
let hole6:Int = Int(holeSixScore.text!)!
let hole7:Int = Int(holeSevenScore.text!)!
let hole8:Int = Int(holeEightScore.text!)!
let hole9:Int = Int(holeNineScore.text!)!
let totalArray = [hole1, hole2, hole3, hole4, hole5, hole6, hole7, hole8, hole9]
let totalScore = totalArray.reduce(0, +)
totalForFrontNine.text = String(totalScore)
print(totalForFrontNine!)
}
It worked, but barely. Any thoughts to modify this or a complete refresh is fine! I am not tied to anything as i am using this project to just teach me the basics.
Thanks in advance, cheers - glassGarrett
Like #jawadali mentioned, use IBOutletCollection. Here is a tutorial about how to use it.
As for the other issue,
...the data type from the textField being a string, where as I will only be dealing with integers.
A quick solution is to set the keyboardType of your textfield to UIKeyboardTypeNumberPad, or .numberPad in Swift.
textField.keyboardType = .numberPad
Or in storyboard,

How do you make a function which sets properties of other instances in swift?

I am trying to write the following code in a shorter way.
func colourChangeIfDeletionCancelled(word:Int){
for i in 0...selectedWords[word].count - 1 {
let square = selectedWords[word][i]
arrayOfRows[square.0][square.1].topToRight.backgroundColor = nil
arrayOfRows[square.0][square.1].topToLeft.backgroundColor = nil
arrayOfRows[square.0][square.1].bottomToRight.backgroundColor = nil
arrayOfRows[square.0][square.1].bottomToLeft.backgroundColor = nil
arrayOfRows[square.0][square.1].horizontalTube.backgroundColor = nil
arrayOfRows[square.0][square.1].verticalTube.backgroundColor = nil
arrayOfRows[square.0][square.1].endFromLeft.backgroundColor = nil
arrayOfRows[square.0][square.1].endFromRight.backgroundColor = nil
arrayOfRows[square.0][square.1].endFromTop.backgroundColor = nil
arrayOfRows[square.0][square.1].endFromBottom.backgroundColor = nil
}
}
This code works but I am sure there is a better (shorter) way to do write it but I am unsure how. I have tried to make a function that takes the subviews as a variable but am lost on how to do that. I'm sure this isn't the hardest problem but I am stuck on it and any help is appreciated.
Edit:
arrayOfRows is just an array of arrays of a class I have created called LetterSquareView
class LetterSquareView: UIView {
var letter:String!
#IBOutlet weak var topToLeft: UIView!
#IBOutlet weak var topToRight: UIView!
#IBOutlet weak var bottomToRight: UIView!
#IBOutlet weak var bottomToLeft: UIView!
#IBOutlet weak var horizontalTube: UIView!
#IBOutlet weak var verticalTube: UIView!
#IBOutlet weak var endFromLeft: UIView!
#IBOutlet weak var endFromRight: UIView!
#IBOutlet weak var endFromTop: UIView!
#IBOutlet weak var endFromBottom: UIView!
#IBOutlet weak var topToLeftWhiteSpace: UIView!
#IBOutlet weak var topToRightWhiteSpace: UIView!
#IBOutlet weak var bottomToRightWhiteSpace: UIView!
#IBOutlet weak var bottomToLeftWhiteSpace: UIView!
#IBOutlet weak var letterSquareViewView: LetterSquareViewView!
#IBOutlet var letterSquareView: UIView!
#IBOutlet weak var letterLbl: UILabel!
init(frame: CGRect, letter: String) {
super.init(frame: frame)
NSBundle.mainBundle().loadNibNamed("LetterSquareView", owner: self, options: nil)
letterLbl.text = letter.capitalizedString
self.letter = letter
self.addSubview(letterSquareView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The subviews that I am trying to change the background colour of have a background colour set when they are created. I am trying to delete that colour when they are deleted. I thought there would be a function where you could input an array of the views and input a property of there's to set and it'd just be set but I can't find anything of the sort.
I guess the most straight forward way is to have:
func clearBackgroundColors(e: WhateverViewThisIs) {
for view in [e.topToRight, e.topToLeft, etc. ] {
view.backgroundColor = nil
}
}
clearBackgroundColors(arrayOfRows[square.0][square.1])
A good question to think is about responsibilities of the classes. I would argue the view in arrayOfRows[][] should have it's subviews private, knowledge of them is likely an implementation detail and not of public knowledge. So the clearBackgroundColors() method should be placed there instead of setting all colors from a more global place.
import UIKit
var arrayOfRows: [[UIView]] = [[]]
let square: (Int, Int)!
A way to make your touple code cleaner:
//extracting touple
let (row, col) = square
arrayOfRows[row][col].topToRight.backgroundColor = nil
Only use this is you are sure all the subviews of this view you want to make the background color nil.
//for all views
for arrayOfColumns in arrayOfRows {
for views in arrayOfColumns {
for subview in views.subviews {
subview.backgroundColor = nil
}
}
}
This is how I recommend to handle it. This also allows you to customize those views further with OOP design. Then you can
//with class type
//use this if there are other subviews
//make all your views in your list a CustomViewToMakeNil
class CustomViewToMakeNil: UIView {
//set the views content
}
for arrayOfColumns in arrayOfRows {
for views in arrayOfColumns {
for subview in views.subviews {
//check if they are of the type you want to make the color nil
if subview is CustomViewToMakeNil {
subview.backgroundColor = nil
}
}
}
}

UIView doesn't center properly in UIScrollView with autolayout

I'm making an application where I need an x amount of custom UIView and place them in a scrollView. The layout of the scrollView and the UIView xib are set with AutoLayout. The result I'm getting now is this:
First View is well centred in ScrollView
Second View is wrong and has a lot of space in between
See my VC Code under here.
let sponsors = Sponsors.createSponsors() <-- Array
override func viewDidLoad() {
super.viewDidLoad()
configureSponsors()
}
//MARK: Load the AddView in ScrollView
func configureSponsors() {
self.scrollView.contentSize = CGSizeMake(CGFloat(sponsors.count) * self.scrollView.frame.size.width, self.scrollView.frame.size.height)
for sponsor in sponsors {
numberOfItems++
let addView = NSBundle.mainBundle().loadNibNamed("AddView", owner: self, options: nil).last as! AddView
addView.addDataToAddView(sponsor)
addView.frame = CGRectMake(CGFloat(numberOfItems - 1) * scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height)
self.scrollView.addSubview(addView)
}
}
And here is my UIView code:
//MARK: Outlets
#IBOutlet weak var backgroundImageView: UIImageView!
#IBOutlet weak var sponsorTitle: UILabel!
#IBOutlet weak var sponsorLogo: UIImageView!
#IBOutlet weak var sponsorSubtitle: UILabel!
#IBOutlet weak var roundView: UIView!
#IBOutlet weak var playMusicButton: UIButton!
//MARK: Properties
var cornerRadius: CGFloat = 3.0
func addDataToAddView(sponsor: Sponsors) {
backgroundImageView.image = UIImage(named: sponsor.backgroundImage)
sponsorLogo.image = UIImage(named: sponsor.logoImage)
sponsorTitle.text = sponsor.title
sponsorSubtitle.text = sponsor.subTitle
}
override func layoutSubviews() {
roundView.layer.cornerRadius = roundView.frame.size.width / 2
roundView.alpha = 0.7
roundView.clipsToBounds = true
}
//MARK: PlayVideo
#IBAction func playVideo(sender: UIButton) {
//play music
}
I've already searched for the same problems. I found out that I have to use the Pure Auto Layout Approach. Should that mean I need to programatically set the constraints for the UIView?
Many thanks,
Dax
Update:
Pictures for scrollView setup:
You should not perform layout calculations in viewDidLoad as frames are not set correctly till then.
Correct place to do this is either viewWillLayoutSubviews or viewDidLayoutSubviews as you will get the correct frame data when these functions are called

UITextView Font Size

I have a blank/empty UITextView that will output a result based on user-inputs. Everything works great with one small detail that's been bugging me - the font size when it delivers the output is too small for my liking! I tried changing it in the "Attributes Inspector" of storyboard but that only affects it if I decide to have text in the box prior to an output.
My code thus far is
class FreePointViewController: UIViewController {
#IBOutlet weak var fpCoilSizeInput: UITextField!
#IBOutlet weak var fpCoilThicknessInput: UITextField!
#IBOutlet weak var fpLengthInput: UITextField!
#IBOutlet weak var fpPullForceInput: UITextField!
#IBOutlet weak var freePointResult: UITextView!
#IBAction func freePointButton(sender: AnyObject) {
var freePointConst:Double = 20684
calcFreePoint.freePointCoilSize = Double((fpCoilSizeInput.text as NSString).doubleValue)
calcFreePoint.freePointCoilThickness = Double((fpCoilThicknessInput.text as NSString).doubleValue)
calcFreePoint.freePointStretchPipe = Double((fpLengthInput.text as NSString).doubleValue)
calcFreePoint.freePointPullForce = Double((fpPullForceInput.text as NSString).doubleValue)
var freePointArea:Double = M_PI * (calcFreePoint.freePointCoilSize - calcFreePoint.freePointCoilThickness) * calcFreePoint.freePointCoilThickness
var freePoint = (freePointConst * calcFreePoint.freePointStretchPipe * freePointArea) / calcFreePoint.freePointPullForce
var freePointFormat = "0.2"
freePointResult.text = "The pipe is stuck at \(freePoint.format(freePointFormat)) meters"
}
I've tried adding code like [textView setFont:[UIFont boldSystemFontOfSize:15]]; just above my freePointResult.text = ... line but to no avail.
I found the answer to my own question...Check if "Selectable" is checked in the xib/Storyboard attribute inspector. If it's not selectable, the size wont be changed

Resources