I have a header of a UICollectionView. This header contains a UILabel. I'm updating the text value of the label by calling a function updateClue through a delegate.
class LetterTileHeader: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var clueLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.text = "HELLO"
label.textColor = UIColor.white
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setupViews() {
backgroundColor = GlobalConstants.colorDarkBlue
addSubview(clueLabel)
clueLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
clueLabel.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
clueLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
clueLabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
}
func updateClue(clue: String) {
clueLabel.text = clue
debugLabel()
}
func debugLabel() {
print(clueLabel.text)
}
The function debugLabel is printing with the changed text coming from updateClue. But the label on the screen does not update. What am I missing? I've tried using a DispatchQueue.main.async block with changing the label text within that but no still no change.
UpdateClue function is triggered from another class via a Protocol Delegation.
extension PuzzleViewController: WordTileDelegate {
func relaySelectedWordId(wordId: String!) {
selectableTiles.updateTileHeaderClue(wordId: wordId)
}
}
Thanks.
UPDATE:
I've figured out what's going on. This subview is created each time I select a new cell. Looking into why that's happening. Will update soon.
The issue I'm having is due to this cell class being initialized multiple times. I believe the label I'm seeing is not the current one being updated, but the previous label that was instantiated. I have a new issue though which I may open a question on if I can't figure it out.
You have two UILabels and/or two LetterTileHeaders. The one you are updating is not the one that is being displayed on the screen.
One way to prove this is to put a breakpoint on the line after let label = UILabel(). It is very likely that the breakpoint will be hit more than once.
Another way would be to look at the screen layout in the debugger and find the memory location of the UILabel that is getting displayed, then put a breakpoint in the updateClue method and compare that label's memory location to the one that is being displayed on the screen.
Follow code seems to be wrong, this mean each time when you use clueLble it will give you fresh UILable instance.
var clueLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.text = "HELLO"
label.textColor = UIColor.white
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
Try doing this :
just replace above code with
var clueLabel = UILabel()
Add following func:
override func awakeFromNib() {
super.awakeFromNib()
label.font = UIFont.systemFont(ofSize: 16)
label.text = "HELLO"
label.textColor = UIColor.white
label.translatesAutoresizingMaskIntoConstraints = false
backgroundColor = GlobalConstants.colorDarkBlue
addSubview(clueLabel)
clueLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
clueLabel.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
clueLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
clueLabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
}
remove setupViews()
Related
I create a label component
class CreateExpertPartsTitleLabel : UILabel{
override init(frame: CGRect) {
super.init(frame: frame)
self.isAccessibilityElement = true
self.accessibilityIdentifier = "create_expert_parts_title_label_id"
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.isAccessibilityElement = true
self.accessibilityIdentifier = "create_expert_parts_title_label_id"
}
func setLabel(text : String){
self.textColor = UIColor.appGenericBlackColor
self.font = UIFont().changeFont(font: .Roboto, type: .bold, size: 18)
self.text = text
self.textAlignment = .center
}
}
Then add a label in xib(tableviewcell) and give this class
Then I just call setLabel in awakeFromNib
#IBOutlet weak var titleLabel: CreateExpertPartsTitleLabel!
override func awakeFromNib() {
super.awakeFromNib()
titleLabel.setLabel(text: NSLocalizedString("create_expert_category_selection_title_label", comment: ""))
}
The problem is
When I run it in a simulator or a real device with the cable plugged in.The given text looks perfectly fine.
When I stop the application from xcode and open it in the simulator, or when I disconnect the cable and open it on the phone, the text does not expand according to the length of the text. It shown like
I realized that the problem is caused by the text of the label in the nib. When I delete the Label text in the Label(First pic), that is, when I leave its text blank in the nib, everything works fine.
Also when I assign trailing value to label, the problem does not appear
I think when we do not run it from xcode or run it manually, the width value in the label does not update as much as the length of the text.
Question is:
I don't think there is a problem with the code I wrote or my constraint settings.Is there something I missed here or is it just a bug?
right now I have a collectionView in which I can add cells by tapping the "addCell".
My goal is that if the user taps the "addCell" a view should appear where the user can type in a title for the cell and select an image like so:
Any idea on how I could realize that?
class ContentCell: UICollectionViewCell {
let testImage: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
return v
}()
let testLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.text = "Test Label"
v.font = UIFont(name: "Avenir Next-Bold", size: 18)
v.textColor = .darkGray
v.textAlignment = .center
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.addSubview(testLabel)
contentView.addSubview(testImage)
NSLayoutConstraint.activate([
testImage.topAnchor.constraint(equalTo: contentView.topAnchor),
testImage.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
testImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
testImage.heightAnchor.constraint(equalToConstant:150),
testLabel.topAnchor.constraint(equalTo: testImage.bottomAnchor,constant: 1),
testLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
testLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
testLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
}
}
so in didSelectItemAtIndexPath you present the view controller to ask for the image image and title, and then in that view controller you declare a delegate or completion handler that when the user presses "done" or "save" in the view controller where they selected the image and title, the delegate or completion handler is then called and then tells the original view controller with the cells to add the item you just create and reload the collectionView. this is pretty standard stuff and this same pattern you'll need to learn how to use since most apps use this this same pattern 100s of times per app. i can show you from Objective-C but won't waste my time showing this is Swift. lmk if you need more help. good luck
oh yes, and for future reference, it's best to stick with delegation since using completion handlers gets very tricky when you encounter cases with nested completions and still need to maintain a weakified then strongified reference to the controller. after two degress of weakifying and strogifying "self" the system will start to randomly deallocate your controllers. Stick with delegation.
I am creating UIStackView and in a cycle creating some amount of UILabel and UISwitch pairs that are added to UIStackView. How now I can get the status of UISwitch is it ON or OFF?
func addAnswrsToTheStack(using answers: [Answer]){
createHorizontalStackView()
horizontalStackView.addArrangedSubview(questionProgressView)
var questionSwitch: UISwitch!
for answer in answers {
createHorizontalStackView()
let label: UILabel = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.textAlignment = .left
label.text = answer.text
horizontalStackView.addArrangedSubview(label)
questionSwitch = UISwitch()
questionSwitch.isOn = false
horizontalStackView.addArrangedSubview(questionSwitch)
}
}
You can try
for (index,answer) in answers.enumerated() {
....
questionSwitch = UISwitch()
questionSwitch.tag = index
questionSwitch.isOn = false
questionSwitch.addTarget(self, action: #selector(switchChanged), for:.valueChanged)
}
#objc func switchChanged(_ mySwitch: UISwitch) {
let value = mySwitch.isOn
print(mySwitch.tag , value)
}
You get the value of a UISwitch the same way you set it, by calling isOn. You can loop through all the views in your stackView, check if the view is a switch with
for view in horizontalStackView.arrangedSubviews {
if view is UISwitch {
// check the value here
}
}
You don't know the state state of a switch for each answer since you only create the switch, but you don't save that association. A clean way would be a wrappertype that has a Answer and a UISwitch. But let's do it without for now.
You need return a map that tells you what switch is related what question. The type [Answer:UISwitch] will work and thats it. You now can look up each Switch and its state for an Answer.
func addAnswrsToTheStack(using answers: [Answer]) -> [Answer:UISwitch] {
createHorizontalStackView()
horizontalStackView.addArrangedSubview(questionProgressView)
var questionSwitch: UISwitch!
var switchMap = [Answer:UISwitch]()
for answer in answers {
createHorizontalStackView()
let label: UILabel = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.textAlignment = .left
label.text = answer.text
horizontalStackView.addArrangedSubview(label)
questionSwitch = UISwitch()
questionSwitch.isOn = false
switchMap[Answer] = questionSwitch
horizontalStackView.addArrangedSubview(questionSwitch)
}
return switchMap
}
I've created a subclass to manage my Theme but is not showing neither on device or simulator.
Here my Header.swift:
import Foundation
import UIKit
class Header: UILabel {
override var textColor: UIColor! {
// White Color
get { return ThemeManager.currentTheme.palette.primary }
set {}
}
override var font: UIFont! {
get { return ThemeManager.currentTheme.textStyle.headerText }
set {}
}
}
Here the implementation: (inside the viewcontroller)
var titleLabel: Header = Header()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
// Background Image over the view
setupBackground()
setupStartButton()
setupTitleLabel()
print(titleLabel.frame)
}
// MARK: - Header
private func setupTitleLabel() {
titleLabel.text = "0.0m"
// titleLabel.font = ThemeManager.currentTheme.textStyle.headerText
// titleLabel.textColor = ThemeManager.currentTheme.palette.primary
view.addSubview(titleLabel)
view.bringSubviewToFront(titleLabel)
setupTitleLabelAutolayout()
}
private func setupTitleLabelAutolayout() {
titleLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
But if I use UILabel instead of Header it works perfectly as expected.
I've also tried to implement init?(coder aDecoder: NSCoder) and init(frame: CGRect) but nothing changed.
If I set a frame on init then shows the text, but not styled and ignoring my constraints.
Surely I'm missing something, but what?
To avoid usefulness answers, here some infos:
The UILabel textColor is white
The background is black and has an image over it.
I've tried to remove the image and all the stuff around except for the label and nothing changed.
That's a poor reason to use subclassing. It doesn't allow you to mix-and-match when appropriate.
Better would be to make an extension:
extension UILabel {
func withHeaderStyle() -> UILabel {
self.textColor = ThemeManager.currentTheme.palette.primary
self.font = ThemeManager.currentTheme.textStyle.headerText
return self
}
}
Then at point of use:
var titleLabel = UILabel().withHeaderStyle()
You can make several of these "withXStyle" methods and at the point of use you can chain them together. That's something you can't do with inheritance.
In general you should only use inherence when you want to change behavior. It's ill suited for changing data.
I've fixed that by editing the Header to this:
import Foundation
import UIKit
class Header: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
setupStyle()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupStyle()
}
private func setupStyle() {
self.textColor = ThemeManager.currentTheme.palette.primary
self.font = ThemeManager.currentTheme.textStyle.headerText
}
}
Basically if I understood right, when I set the getter in the label it doesn't (if you think about, it's quite obvious) anything.
I still think that there are better solutions, but this works fine for me so, I'll keep that.
Now you may ask: "Why did you overwritten the getter instead of doing this?"
It's the right question, and the right answer is that I read it in a swift article on medium, so I tought it was right.
PS: I've also tried with didSet but it obviously loop through it self and crash.
I have a subclass of UIView called MyView that I am creating.
In it there is a UILabel (this is added in code not in InterfaceBuilder for reasons).
I then have a property on MyView called color.
What I'd like is to have Interface Builder be able to select the color and also to then display the label with the font set to that color.
In my code I have...
#IBDesignable class MyView: UIView {
private let textLabel = UILabel()
#IBInspectable var color: UIColor = .blackColor() {
didSet {
textLabel.textColor = color
}
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialSetup()
}
override init(frame: CGRect) {
super.init(frame: frame)
initialSetup()
}
private func initialSetup() -> Void {
self.backgroundColor = .clearColor()
textLabel.textAlignment = .Center
textLabel.numberOfLines = 0
addSubview(textLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
textLabel.frame = bounds
}
// I have no idea what I'm doing here?!
override func prepareForInterfaceBuilder() {
// should I create a label specifically for here?
// or should I use the property textLabel?
textLabel.text = "Hello, world!" // setting place holder IB text?
textLabel.textColor = color
textLabel.font = .systemFontOfSize(21)
textLabel.textAlignment = .Center
textLabel.numberOfLines = 0
// do I need to add sub view?
}
}
IBInspectable
In IB I can see the color property there and I can set it. Annoyingly it takes a default value of .clearColor() though not the color I set in code. Is there a way to fix that?
IBDesignable
When I put the view into InterfaceBuilder it shows all the inspectable properties but nothing is shown in the actual view.
When I run it it all works fine. I'd just like to be able to get something working (other than drawRect) in IB. I'm finding a distinct lack of documentation though.
Edit - Warnings
I just noticed that I'm getting build errors / warnings saying...
warning: IB Designables: Ignoring user defined runtime attribute for key path "color" on instance of "UIView". Hit an exception when attempting to set its value: [ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key color.
This will probably change the solution :)
I copied the code from your question into a new app, added the view and set the property and it worked just fine.
The error you're seeing suggests that at some point you've changed the view back to a plain UIView in the identity inspector (the user defined attributes remain in that case).
When color is set you need to update the textLabel property like below using a didSet
#IBInspectable var color: UIColor = UIColor.blackColor() {
didSet {
textLabel.textColor = color
}
}
I am using swift 2.0 and in my case was missing dynamic property. Final code:
#IBInspectable dynamic var radius:CGFloat = 2.0 {
didSet {
// setNeedsDisplay()
layer.cornerRadius = radius
}
}
Find out error during build for IB Designables: http://lamb-mei.com/537/xcode-ibdesignables-ignoring-user-defined-runtime-attribute-for-key-path-bordercolor-on-instance-of-uiview-this-class-is-not-key-value-coding-compliant-for-the-key-bordercolor/