Stackview label not adding programmatically - swift

I'm trying to do everything without a story board but keep getting nil when adding my label but I can't see why. Im sure its dead simple and I'm being blind but I just can't see it after a lot of googling.
View Controller:
class SquaresViewController: UIViewController {
let stackView = OAStackView()
let titleView = TitleView(frame: CGRect.zero)
#IBOutlet var squaresCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(titleView)
titleView.snp.makeConstraints { make in
make.height.equalTo(50)
make.top.equalTo(view.snp.top)
make.left.equalTo(view.snp.left)
make.right.equalTo(view.snp.right)
}
}
}
titleView:
import UIKit
class TitleView: UIView {
#IBOutlet weak var titleText: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
createView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createView() {
titleText = UILabel(frame: CGRect.zero)
titleText.translatesAutoresizingMaskIntoConstraints = false
titleText.text = "Tappy tap tap a square!"
titleText.textAlignment = .center
titleText.numberOfLines = 0
self.addSubview(titleText)
titleText.snp.makeConstraints { make in
make.edges.equalTo( self.snp.edges )
}
}
}
When building it fails at:
titleText.translatesAutoresizingMaskIntoConstraints = false
with:
fatal error: unexpectedly found nil while unwrapping an Optional value

First of all, I don't understand why you created an IBOutlet for titleText if you don't want to use storyboards. If you don't want to use storyboards, change the implementation to:
var titleText : UILabel!
Secondly, the application is crashing because you are asking it to perform an operation on a view that is not yet added to the hierarchy. For ease of reading I have commented out all lines except those that are important. See below:
func createView() {
//titleText = UILabel(frame: CGRect.zero)
titleText.translatesAutoresizingMaskIntoConstraints = false // <-- You are trying to perform an operation on titleText here
//titleText.text = "Tappy tap tap a square!"
//titleText.textAlignment = .center
//titleText.numberOfLines = 0
self.addSubview(titleText) // <-- but you are adding it to the view hierarchy here
//titleText.snp.makeConstraints { make in
//make.edges.equalTo( self.snp.edges )
//}
}
titleText has to be added to the view hierarchy before you can call translatesAutoresizingMaskIntoConstraints on it, like so:
self.view.addSubview(titleText)
titleText.translatesAutoresizingMaskIntoConstraints = false // <-- Here we are waiting until the view has been added to the hierarchy to perform operations on it
So try changing implementation to look like so:
import UIKit
class TitleView: UIView {
var titleText: UILabel!
override init(frame: CGRect) {
// leave as is, removed for space
}
required init?(coder aDecoder: NSCoder) {
// leave as is, removed for space
}
func createView() {
titleText = UILabel(frame: CGRect.zero)
titleText.text = "Tappy tap tap a square!"
titleText.textAlignment = .center
titleText.numberOfLines = 0
self.addSubview(titleText)
titleText.translatesAutoresizingMaskIntoConstraints = false
titleText.snp.makeConstraints { make in
make.edges.equalTo( self.snp.edges )
}
}
}

Related

UIViewRepresentable remains white

I have this very basic setup:
struct ConversationChatMessagesWrapper: UIViewRepresentable {
private let view = ConversationChatViewWithSendMessage()
func makeUIView(context _: Context) -> ConversationChatViewWithSendMessage {
view
}
func updateUIView(
_: ConversationChatViewWithSendMessage,
context _: Context
) {}
}
class ConversationChatViewWithSendMessage: UIView {
#available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("Not used")
}
init() {
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .red
let otherView = UIView()
otherView.backgroundColor = .blue
otherView.translatesAutoresizingMaskIntoConstraints = false
addSubview(otherView)
otherView.topAnchor.constraint(equalTo: topAnchor).isActive = true
otherView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
otherView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
otherView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
}
}
I haven't worked with UIKit in a while, but I thought it should see a blue screen, or at least a red screen, but my screen remains white. When I remove the call which adds the otherView to ConversationChatViewWithSendMessage, I see a blue screen, so something is wrong while adding otherView, but I can't see what. I even replicated this setup in Storyboard and that just worked fine.
Any suggestions?
This is a screenshot from Xcode, the warning states:
Position and size are ambigious
Remove this line after the super.init.
translatesAutoresizingMaskIntoConstraints = false

Set a bool value using model object didSet

I'm struggling to figure out how to properly set bool values using a model object's didSet. My app has a series of swipable cards where some flip and some don't. This code below is the CardView which is run for each card created.
Currently, the code works perfectly for the image and label—each card loads unique information based each card's model object. However, the button and isFlippable property are where I'm struggling.
The code right now is always loading the green pathway. The weird thing, however, is that even when the cardModel should sets the button isEnabled to false, it will still load the green (but the button won't work, so it did become disabled...)
var cardModel: CardModel! {
didSet {
imageView.image = cardModel.image
label.text = cardModel.label
flipButton.isEnabled = cardModel.isFlippable
isBackShowing = cardModel.isFlippable //Intentionally use isFlippable here because I want the initial layout to be based on this true or false value.
}
}
let imageView = UIImageView()
let label = UILabel()
let flipButton = UIButton()
var isBackShowing = false
override init(frame: CGRect) {
super.init(frame: frame)
setupLayout()
}
fileprivate func setupLayout() {
if flipButton.isEnabled == true {
if isBackShowing == true {
backgroundColor = .red
} else {
backgroundColor = .green
}
} else {
backgroundColor = .yellow
}
}
I also have code for when the button flips that alternates "isBackShowing" and then calls setupLayout()—it is working fine. But it always loads as false during the initial setup of the card.
For better readability you can little bit update your code replacing var isBackShowing = Bool() by var isBackShowing = false.
And also you can call setupLayout() to update your layout after setting of cardModel. For example didSet of cardModel can looks like this:
var cardModel: CardModel! {
didSet {
imageView.image = cardModel.image
label.text = cardModel.label
flipButton.isEnabled = cardModel.isFlippable
isBackShowing = cardModel.isFlippable
setupLayout()
}
}

Label Colours on an #IBDesignable View appearing different of different devices

I have an #IBDesignable custom view. It contain an UIView with two subclassed UILabels. The custom subclass of UILabel is setting it's font.
What I'm trying to achieve is by changing by making the background colour of the view an inspectable property that the text colour change appropriately to be legible.
My code is below. Custom.Colour.<name> is just an enum of defined colours.
#IBDesignable
class CustomMiniView: UIView, NibLoadable {
public var view:UIView!
#IBOutlet weak var colourView: UIView!
#IBOutlet weak var headingLabel: CustomUILabel!
#IBOutlet weak var amountLabel: CustomUILabel!
#IBInspectable var blockColor:UIColor! {
didSet {
self.colourView.backgroundColor = blockColor
switch blockColor {
case Custom.Colour.darkBlue:
headingLabel.textColor = .white
amountLabel.textColor = .white
case Custom.Colour.blue:
headingLabel.textColor = .white
amountLabel.textColor = .white
case Custom.Colour.lightBlue:
headingLabel.textColor = Custom.Colour.grey
amountLabel.textColor = Custom.Colour.blue
case Custom.Colour.green:
headingLabel.textColor = .white
amountLabel.textColor = .white
case Custom.Colour.lightGreen:
headingLabel.textColor = Custom.Colour.grey
amountLabel.textColor = Custom.Colour.blue
case Custom.Colour.yellow:
headingLabel.textColor = Custom.Colour.grey
amountLabel.textColor = Custom.Colour.grey
default : printError("We have not handled text colours for a background of this colour. = \(blockColor.hexString)")
}
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
self.setupFromNib()
self.commonInit()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupFromNib()
self.commonInit()
}
func commonInit() {
self.backgroundColor = .clear
}
}
This was working for me fine, however I was then getting report that the text was showing up as white everywhere and got sent a screen shot which confused me. When I ran this in a simulator and a different device I was able to see this not working. Here are two screen shots of what is happening on my iPad and what I expect to happen and a screen shot of what is happening on some other devices and the simulator.
This is what is happening on my device and the expected result.
This is whats happening on other devices and the incorrect result.
Is there a reason this would appeared different of different devices? I'm at a loss to the cause or how to fix this.
Thanks to comments above I've found a fix.
Rather than doing a switch on the colour I've done a switch on the .hexString of the colour which has worked.
switch blockColor.hexString {
case Custom.Colour.darkBlue.hexString :
for the record hexString is an extension to UIColor
var hexString: String {
let colorRef = cgColor.components
let r = colorRef?[0] ?? 0
let g = colorRef?[1] ?? 0
let b = ((colorRef?.count ?? 0) > 2 ? colorRef?[2] : g) ?? 0
let a = cgColor.alpha
var color = String(
format: "#%02lX%02lX%02lX",
lroundf(Float(r * 255)),
lroundf(Float(g * 255)),
lroundf(Float(b * 255))
)
if a < 1 {
color += String(format: "%02lX", lroundf(Float(a)))
}
return color
}
I'm still not sure why switching on hexString worked and the UIColor in the enum did not work consistently but thanks to comments about that helped me find a solution to fix it myself. Question for another time is why switching on UIColor would be unreliable but now back to work.

changing the multiplier value of a constraint

I have a menu bar which sits at the top of a CollectionViewController with several different titles about 30 pixels high
. Each title has a small bar underneath, indicating which page/item the user is on, similar to the Youtube app.
class MenuBar : UIView {
var menuSectionTitles = ["Title1","Title2", "Title3","Title4"]
var numberOfSectionsInHorizontalBar : CGFloat?
var horizontalBarLeftAnchorConstraint: NSLayoutConstraint?
var horizontalBarWidthAnchorConstraint: NSLayoutConstraint?
override init(frame: CGRect) {
super.init(frame: frame)
setupHorizontalBar()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupHorizontalBar() {
let horizontalBarView = UIView()
horizontalBarView.backgroundColor = UIColor.rgb(10, green: 150, blue: 255)
horizontalBarView.translatesAutoresizingMaskIntoConstraints = false
addSubview(horizontalBarView)
horizontalBarLeftAnchorConstraint = horizontalBarView.leftAnchor.constraint(equalTo: self.leftAnchor)
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalBarView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
// Set the multiplier/width of the horizontal bar to indicate which page the user is on
horizontalBarWidthAnchorConstraint = horizontalBarView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1 / 4)
horizontalBarWidthAnchorConstraint?.isActive = true
horizontalBarView.heightAnchor.constraint(equalToConstant: 3).isActive = true
}
}
The multiplier value for horizontalBarWidthAnchorConstraint is hard coded in at a value of 1/4
I want to reuse the view multiple times across different view controllers, therefore have attempted to change the multiplier of the horizontalBarWidthAnchorConstraint like so:
class ViewController: UICollectionViewController {
lazy var menuBar : MenuBar = {
let mb = OptionsBar()
mb.menuOptions = ["Title1", "Title2", "Title3"]
mb.translatesAutoresizingMaskIntoConstraints = false
mb.horizontalBarWidthAnchorConstraint?.constant = 1/3
return mb
}()
}
But this method does not work. I can not find a way to change the multiplier value from inside the ViewController.
In the code here you are changing the constant of the constraint, not the multiplier. Is that correct?
Also note, the multiplier of a constraint is read only. You cannot change it. If you want to change the multiplier of a constraint applying to a view then the only to do it is to add a new constraint with the new multiplier and remove the old constraint.
The only part of a constraint that is read/write is the constant property.

Check text field Live

I have found this answer How to check text field input at real time?
This is what I am looking for. However I am having trouble actually implementing this code. Also my current geographical location makes googling almost impossible.
I want to be able to change the background color of the next text field if the correct number is entered into the previous text field. textfieldTwo background color will change to green if the correct value is entered in textFieldOne. If the value is incorrect then nothing will happen. Please help me out. I have two text fields called textFieldOne and textFieldTwo and nothing else in the code.
Just pop this in your main view controller in an empty project (try using iphone 6 on the simulator)
import UIKit
class ViewController: UIViewController {
var txtField:UITextField!
var txtFieldTwo:UITextField!
var rightNumber = 10
override func viewDidLoad() {
super.viewDidLoad()
//txtFieldOne
var txtField = UITextField()
txtField.frame = CGRectMake(100, 100, 200, 40)
txtField.borderStyle = UITextBorderStyle.None
txtField.backgroundColor = UIColor.blueColor()
txtField.layer.cornerRadius = 5
self.view.addSubview(txtField)
//txtFieldTwo
var txtFieldTwo = UITextField()
txtFieldTwo.frame = CGRectMake(100, 150, 200, 40)
txtFieldTwo.borderStyle = UITextBorderStyle.None
txtFieldTwo.backgroundColor = UIColor.blueColor()
txtFieldTwo.layer.cornerRadius = 5
self.view.addSubview(txtFieldTwo)
txtField.addTarget(self, action: "checkForRightNumber", forControlEvents: UIControlEvents.AllEditingEvents)
self.txtField = txtField
self.txtFieldTwo = txtFieldTwo
}
func checkForRightNumber() {
let number:Int? = self.txtField.text.toInt()
if number == rightNumber {
self.txtFieldTwo.backgroundColor = UIColor.greenColor()
} else {
self.txtFieldTwo.backgroundColor = UIColor.blueColor()
}
}
}
EDIT: Adding a version with IBOutlets and IBActions
Note that in this example the IBAction is connected to txtFieldOne on Sent Events / Editing Changed
Also, make sure your Text Fields border colors are set to None. In the storyboard, the way to do this is to choose the left most option with the dashed border around it. That's so you can color the backgrounds. You can use layer.cornerRadius to set the roundness of the border's edges.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var txtField: UITextField!
#IBOutlet weak var txtFieldTwo: UITextField!
var rightNumber = 10
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func checkForRightNumber(sender: AnyObject) {
let number:Int? = self.txtField.text.toInt()
if number == rightNumber {
self.txtFieldTwo.backgroundColor = UIColor.greenColor()
} else {
self.txtFieldTwo.backgroundColor = UIColor.blueColor()
}
}
}