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.
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?
I'm trying to make my app more accessible for Voice Over users. I have a slider that has numbers 1-100. If a user with Voice Over turned on swipes up or down to change the value, several numbers are being skipped. This means that an exact number is not able to be set. I'm following the suggestion from this site on subclassing UISlider and overriding accessibilityIncrement() and accessibilityDecrement() but they do not get called when the slider value changes. Below is my subclassed slider. Any idea why the methods are not getting called?
class FontSizeSlider: UISlider {
required init?(coder: NSCoder) {
super.init(coder: coder)
self.isAccessibilityElement = true
self.accessibilityTraits.insert(.adjustable)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func accessibilityIncrement() {
self.value += 1
self.sendActions(for: .valueChanged)
}
override func accessibilityDecrement() {
self.value -= 1
self.sendActions(for: .valueChanged)
}
}
This is something I need to know for work, so this was a fantastic exercise for me. Thank you for posting the question. Anyway, I got it to work after taking a peek at this page on Apple's website.
I could not get the increment/decrement methods to be called, either. I suspect they're stepper-specific. The value property, OTOH, gets called.
Here's the code I came up with to get it to work:
class FontSizeSlider: UISlider {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
isAccessibilityElement = true
accessibilityLabel = "Font Size Slider"
accessibilityIdentifier = "fontSizeSlider"
// accessibilityIdentifier = AccessibilityConstants.fontSizeSlider.rawValue
minimumValue = 0
maximumValue = 100
isContinuous = true
}
override var accessibilityValue: String? {
get {
return sliderValueString
}
set {
super.accessibilityValue = sliderValueString
}
}
override var accessibilityTraits: UIAccessibilityTraits {
get {
return .adjustable
}
set {
super.accessibilityTraits = newValue
}
}
// Nobody needs to know about this outside the class, so marked it private
private var sliderValueString: String {
let stringValue = String(Int(value))
return "The font size is \(stringValue)"
}
}
You'll notice I used the setup() method, which does the same stuff for both initializers. You can tweak your values as you see fit for the min/max values.
You'll note I added accessibilityLabel, so it doesn't read off that it's a generic slider. I added the accessibilityIdentifier in there, too. That's something that can be used for UI tests so the element can be identified.
You'll probably want to put the accessibilityIdentifier somewhere where "everyone" can see it. Perhaps an enum. Here's what the enum implementation would look like:
enum AccessibilityConstants: String {
case fontSizeSlider
}
// Usage
accessibilityIdentifier = AccessibilityConstants.fontSizeSlider.rawValue
I overrode the accessibilityValue with a custom setter and getter. Additionally, I created a computed var for the string that's read off when the accessibilityValue is updated. Here's the code for that portion of it. Note I made it private because nobody outside the class needs to know about it:
// I adapted this from Apple's accessibility page that I posted above
override var accessibilityValue: String? {
get {
return sliderValueString
}
set {
super.accessibilityValue = sliderValueString
}
}
private var sliderValueString: String {
let stringValue = String(Int(value))
return "The font size is \(stringValue)"
}
One last thing...you don't need self everywhere unless you're accessing a property of your custom UISlider inside a closure like an animation block or a completion block.
Update
Deleted...
Update 2
So let's say you're on your viewController, you could add a target action to the slider, like so:
slider.addTarget(self, action: #selector(doSomething), for: .valueChanged)
#objc func doSomething() {
print("How much wood could a wood chuck chuck if a wood chuck could chuck wood")
}
Whenever value changes, your selector will get called.
I am creating a subclass of UIStackView, and I want spacing to be static and non-overwritable. I thought this would be simply done by overriding the spacing property:
class MyStackView: UIStackView {
override var spacing: CGFloat {
get { return 8.0 }
set {}
}
}
This, however, does not work. The getter is never called and no spacing is displayed. The only solution I found for this is the following piece of code:
class MyStackView: UIStackView {
override init(frame: CGRect) {
super.init(frame: frame)
super.spacing = 8.0
}
override var spacing: CGFloat {
get { return 8.0 }
set {}
}
}
It actually doesn't matter what I return in get, it's never called by anything anyway.
Can anybody explain this behavious]r and how to avoid it?
In my opinion here what's happening. You want to achieve two things:
Spacing at MyStackView should be 8.0 by default
That spacing value should not be changeable from outside
To achieve the first thing you need to override the initializers and set your default value inside on them.
class MyStackView: UIStackView {
private let defaultSpacing: CGFloat = 8.0
override init(frame: CGRect) {
super.init(frame: frame)
super.spacing = defaultSpacing
}
required init(coder: NSCoder) {
super.init(coder: coder)
super.spacing = defaultSpacing
}
}
For the second thing, you need to override the getter and setter of spacing to get the control on any changing from the outside.
class MyStackView: UIStackView {
...
override var spacing: CGFloat {
get { return defaultSpacing }
set { print("MyStackView spacing it always \(defaultSpacing)") }
}
}
Why can't you simply override a variable and get the same result?
UIStackView.spacing property is computed, it's not a container. We might not care how does original getter works because we know that our spacing is always the same. We know its value and can always return it at overriden getter.
But we also don't know how does original setter works and what changes does it make in UIStackView. Since that, we can't simply override getter. We have to explicitly call super.spacing = defaultValue once, to trigger original setter and set the desired value.
I am new on iOS and Swift and I need some help.
I want create custom UIButton
Here is what I did
protocol ButtonProtocol {}
extension ButtonProtocol where Self: UIButton {
func addOrangeButton(){
layer.cornerRadius = 8
layer.backgroundColor = UIColor(netHex:ButtonColor.orange).cgColor
}
}
I want all params came from here which are cornerRadius, backgrounColor, highlightedColor, textColor, size etc...
I want use this way bcoz maybe in future the button color will change I will change it from one place directly.
But I don't understand what is layer how could I cast it as UIButton?
Is anyone can tell me which way should I take ?
You can create the subclass of UIButton, to add your own custom look to your button. like this
import UIKit
protocol DVButtonCustomMethods: class {
func customize()
}
class DVButton: UIButton {
var indexPath: IndexPath?
override init(frame: CGRect) {
super.init(frame: frame)
customize()// To set the button color and text size
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
customize()// To set the button color and text size
}
override func layoutSubviews() {
super.layoutSubviews()
customize()
}
}
extension DVButton: DVButtonCustomMethods {
func customize() {
layer.cornerRadius = self.frame.size.height / 2
backgroundColor = UIColor.white
tintColor = UIColor.red
titleLabel?.textColor = UIColor.black
clipsToBounds = true
}
}
Now what is need to do is, create one button in interface builder and assign you subClass as its class. Thats all everything will change as you want. If you want to change button colour just change in your subclass, it will affect in all button which is assigned your subclass.
Assigning subclass to your button: Refer below image
Thanks:)
The way you defined the extension, doesn't make you able to use it in the UIButton instance so simple.
So, you can decide whether extend UIButton to conform the protocol, or you can create a subclass of UIButton
// in this way you can use the `addOrangeButton` method anywhere
extension UIButton: ButtonProtocol {}
// in this way your new subclass contains the addOrangeButton definition
// and a normal UIButton cannot access that method
final class OrangeButton: UIButton, ButtonProtocol {
func setupButton() {
addOrangeButton()
}
}
Try this:
class func CutomeButton(bgColor: UIColor,corRadius: Float,hgColor: UIColor, textColor: UIColor, size: CGSize, titleText: String) -> UIButton {
let button = UIButton()
button.layer.cornerRadius = CGFloat(corRadius)
button.backgroundColor = bgColor
button.setTitleColor(textColor, for: .normal)
button.frame.size = size
button.setTitle(titleText, for: .normal)
return button
}
If I understand well, you want to modify a UIButton with specific parameters, let me tell you how do I do this:
extension UIButton
{
func setRadius(radius:CGFloat) {
self.layer.cornerRadius = radius
}
}
Use it as the following:
yourButton.setRadius(radius: 15)
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/