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?
Related
I would like to ask, have any ways to change all Font, Size for Label by single code?
Like Dynamic Type inside the app.
ex: Android have been supported with the XML file (dimens)
Thank you!
There are a few ways to achieve that
You can do something like this to give the same styling for all UILabels:
extension UILabel {
#nonobjc
convenience init() {
self.init(frame: .zero)
self.font = UIFont.systemFont(ofSize: 12, weight: UIFontWeightThin)
//... here goes other changes
}
}
You will need to use a convenience init every time.
Another option would be to create some custom UILabel:
class CustomLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
self.font = UIFont.systemFont(ofSize: 12, weight: UIFontWeightThin)
//... here goes other changes
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.font = UIFont.systemFont(ofSize: 12, weight: UIFontWeightThin)
//... here goes other changes
}
}
The last option is relatively more complex and will require you to create customised way of handling different stylings for UILabel.
I have a custom UILabel class that automatically scales the font using a font metric and size that's chosen by the user through Settings > General > Accessibility > Larger Text:
import UIKit
class AccessibilityLabel: UILabel {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
font = UIFontMetrics.default.scaledFont(for: font)
adjustsFontForContentSizeCategory = true
}
}
In our existing, fairly large project, this works great.
Where it does not work is where the font is set in code. When the font is set in code it removes the scaling. We could go through and change all the code where we set the font to scale it. And maybe this is the best/only way.
But I was wondering if anyone knew of a way I could override the font property of the UILabel so it scales it if not already scaled and so doesn't create an endless loop. Basically, some solution where we would not have to go through our existing code and update multiple places. Just make the change in one place (in the UILabel).
Thanks!
Here's what I did in a blank project {Xcode 11, iOS 13} containing only one label:
class AccessibilityLabel: UILabel {
private var _font: UIFont?
override var font: UIFont? {
get { return _font }
set { _font = UIFontMetrics.default.scaledFont(for: newValue!) }
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.adjustsFontForContentSizeCategory = true
}
}
Then, I changed the font programmatically in my ViewController as follows:
//#IBOutlet weak var myLabel: AccessibilityLabel!
myLabel.font = UIFont(name: "NoteWorthy", size: 20.0)
... to get the results in different size text hereafter:
If I perfectly understood your problem, I think this solution should help you making the UILabel custom only in one place without going through your existing code.
... otherwise, just let me know if there's something I misunderstood, please.
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 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()
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/