UITextView does not change textColor property - ios

I have a UITextView custom class:
class TitleTextView: UITextView {
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
func setup() {
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
textColor = .brand100
backgroundColor = .clear
isUserInteractionEnabled = false
textAlignment = .left
isScrollEnabled = false
let frameWidth = Constants.screenSize.width * 87.5 / 100
font = UIFont.OpenSans(.semibold, size: (frameWidth * 8.55 / 100))
}
}
I used this text view custom class inner a UIView.
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
And I called this UIView in a UIViewController.
private func setupTitleView() {
let titleView = UINib(nibName: "TitleView", bundle: .main).instantiate(withOwner: nil, options: nil).first as! UIView
titleView.frame = contentHeaderView.bounds
contentHeaderView.addSubview(titleView)
view.layoutIfNeeded()
}
But when I set the textColor property in my custom UIView (MyCustomHeaderView) the color doesn't change.
Do you have any idea about why the reason that my UITextView doesn't apply the color that I set in my custom UIView?
 
I called layoutIfNeed() but this doesn't work.

It's because you are doing everything inside the layoutSubviews Which in itself is really bad practice.
In your case you instantiate the CustomHeaderView and the layout for that is called, hence calling layoutSubviews next step is that the textView is added to your CustomHeaderView and then the textView's layoutSubviews is called and will override your color.
You can solve this in two ways i believe. Altho i don't work with Nibs and storyboards,
first:
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
Second, this is a big maybe:
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
defer {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
}
Defer will wait till everything is been initialised before running whatever is in the block. I don't know tho how that works with layoutSubviews

Related

How to change UITextFields default border color

How can I set a default border color for UITextFields which are not in focus when the view appears? Tried this with no success:
class UITextFieldCustom : UITextField, UITextFieldDelegate {
init(frame: CGRect, size: CGFloat) {
super.init(frame: frame)
self.layer.borderColor = UIColor.gray.cgColor
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
If you can't see new border, try to increase its width:
layer.borderWidth = 2.0
The problem you are facing is that self.layer.borderColor = UIColor.gray.cgColor is not called, because the function init(frame: CGRect, size: CGFloat) is not called. The reason why it is not called is because your textfield is not initilized in your code, for example:
func createTextField() -> UITextfield {
return UITextfield(frame: .zero, size: 100.0)
}
Your textfield is initilized from the storyboard. That is why the other init function required init?(coder aDecoder: NSCoder) exists. The sotryboard calls this function to initalize the textfield.
To solve the problem you just need to add your border modification line into the other init function, like this:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.borderColor = UIColor.gray.cgColor
}
Although one other clean approach I prefere more, is to add all setup code into the viewDidLoad method, for a clean overview, like this:
class ViewController: UIViewController {
// MARK: - Properties
#IBOutlet weak var textfield: UITextfield!
// MARK: - View's Lifecycle
override func viewDidLoad {
super.viewDidLoad()
setupTextfield()
}
// MARK: - Setup
private func setupTextfield() {
textfield.layer.borderColor = UIColor.gray.cgColor
}
}
just place your code here as well, you probably don't call this from storyboard or nib.
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.borderColor = UIColor.gray.cgColor
}
use #IBDesignable class to create the border color and set the width, like this example.
import UIKit
#IBDesignable
class BorderTextField: UITextField {
#IBInspectable var borderColor: UIColor = UIColor.white{
didSet{
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet{
layer.borderWidth = borderWidth
}
}
}
the you can set the value in storyboard as needed.

Add subview in interfaceBuilder

I am trying to add a label to UIView and I would like to preview it in InterfaceBuilder, however it doesn't seem to work. not only I don't see label but It also doesn't append one to view hierarchy
#IBDesignable
class UIFloatingLabelInput: UIView {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
let Label : UILabel?
func AddLabel(){
Label?.text = "Hello World"
Label?.center = CGPoint(x: self.center.x, y: self.center.y)
self.addSubview(Label!)
}
override init(frame: CGRect){
//Init vars
Label = UILabel(frame: frame)
//Call Initializer
super.init(frame: frame)
AddLabel()
}
required init?(coder: NSCoder){
//Init vars
Label = UILabel(coder: coder)
//CallInitializer
super.init(coder: coder)
AddLabel()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
AddLabel()
}
}
but it doesn't work in IB, but works fine on a device.
You need to give your label frame to make it work in IB
required init?(coder: NSCoder){
//Init vars
Label = UILabel(coder: coder)
//CallInitializer
super.init(coder: coder)
//Give your label a frame
Label.frame = bounds
AddLabel()
}

How to see changes when adding Custom Class to UILabel, in storyboard itself?

I have added a custom class to UILabel.
Custom Class is:
#IBDesignable class CustomLabel: UILabel {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
}
private func setup() {
self.textColor = UIColor.blue
}
}
But I cant see the changes in storyboard. How can it is able to see the changes in the interface builder??
You have to add IBInspectable properties to see changes in storyboard
here is example
#IBDesignable class RoundedTextField: UITextField {
#IBInspectable var cornerRadius:CGFloat = 0 // You will see this in storyboard
#IBInspectable var borderColor:UIColor = .green // You will see this in storyboard
override func awakeFromNib() {
super.awakeFromNib()
self.borderStyle = .none
self.layer.cornerRadius = cornerRadius
self.layer.borderColor = borderColor.cgColor
self.layer.borderWidth = 2.0
self.backgroundColor = UIColor.white
self.layer.masksToBounds = true
}
}
Don't forgot to set class
Here how you can see in stoyrboard

UILabel does get roundedCorners

I have extended the UIView class and added a property for cornerRadius. The property does set to desired value. I have made two custom classes one derives from UITextField and another from UILabel. UITextField gets rounded corners but UILabel does not.
Any help in this regard will be highly appreciated.
#IBDesignable
public class BLabel: UILabel {
public override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = cornerRadius
layer.masksToBounds = true
clipsToBounds = true
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
extension UIView {
#IBInspectable
var cornerRadius : CGFloat {
get {return layer.cornerRadius}
set {layer.cornerRadius = newValue}
}
}
In your BLabel class you access the cornerRadius property of your UIView extension in the init method. This is before you have any chance to set a specific corner radius value so it will be 0.
There's no point to the line layer.cornerRadius = cornerRadius in the init method of BLabel. Simply create the BLabel instance and then set its cornerRadius property.
let label = BLabel(frame: someFrame)
label.cornerRadius = 5
Are you sure your UITextField is responding to cornerRadius? Or are you maybe just seeing the normal rounded corners?
Try changing your BLabel to this - it will make sure the initializations are being called properly:
#IBDesignable
public class BLabel: UILabel {
public override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
public override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
public override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
func commonInit() {
// As noted by "rmaddy" ---
// setting .cornerRadius here does nothing, as it is always equal to Zero
// the UIView extension will handle it
//layer.cornerRadius = cornerRadius
layer.masksToBounds = true
clipsToBounds = true
// the following just makes it easy to confirm
// that this code is being executed
backgroundColor = UIColor.red
textColor = UIColor.yellow
textAlignment = .center
}
}
I would like to thank #rmaddy for help. I am writing this for the benefit for all. The code given by rmaddy works. But, after testing it I figured out that it is not required. Just setting layer.masksToBounds = true in UIView extension cornerRadius setter method does the trick. So the entire problem was solved by just this one line of code.
So the final code looks like this and it works:
#IBDesignable
public class BTextField: UITextField {
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
#IBDesignable
public class BLabel: UILabel {
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
extension UIView {
#IBInspectable
var cornerRadius : CGFloat {
get {return layer.cornerRadius}
set {layer.cornerRadius = newValue
layer.masksToBounds = true}
}
}
I hope it helps others also.

How do I write a custom init for a UIView subclass in Swift?

Say I want to init a UIView subclass with a String and an Int.
How would I do this in Swift if I'm just subclassing UIView? If I just make a custom init() function but the parameters are a String and an Int, it tells me that "super.init() isn't called before returning from initializer".
And if I call super.init() I'm told I must use a designated initializer. What should I be using there? The frame version? The coder version? Both? Why?
The init(frame:) version is the default initializer. You must call it only after initializing your instance variables. If this view is being reconstituted from a Nib then your custom initializer will not be called, and instead the init?(coder:) version will be called. Since Swift now requires an implementation of the required init?(coder:), I have updated the example below and changed the let variable declarations to var and optional. In this case, you would initialize them in awakeFromNib() or at some later time.
class TestView : UIView {
var s: String?
var i: Int?
init(s: String, i: Int) {
self.s = s
self.i = i
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I create a common init for the designated and required. For convenience inits I delegate to init(frame:) with frame of zero.
Having zero frame is not a problem because typically the view is inside a ViewController's view; your custom view will get a good, safe chance to layout its subviews when its superview calls layoutSubviews() or updateConstraints(). These two functions are called by the system recursively throughout the view hierarchy. You can use either updateContstraints() or layoutSubviews(). updateContstraints() is called first, then layoutSubviews(). In updateConstraints() make sure to call super last. In layoutSubviews(), call super first.
Here's what I do:
#IBDesignable
class MyView: UIView {
convenience init(args: Whatever) {
self.init(frame: CGRect.zero)
//assign custom vars
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
private func commonInit() {
//custom initialization
}
override func updateConstraints() {
//set subview constraints here
super.updateConstraints()
}
override func layoutSubviews() {
super.layoutSubviews()
//manually set subview frames here
}
}
Swift 5 Solution
You can try out this implementation for running Swift 5 on XCode 11
class CustomView: UIView {
var customParam: customType
var container = UIView()
required init(customParamArg: customType) {
self.customParam = customParamArg
super.init(frame: .zero)
// Setting up the view can be done here
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView() {
// Can do the setup of the view, including adding subviews
setupConstraints()
}
func setupConstraints() {
// setup custom constraints as you wish
}
}
Here is how I do it on iOS 9 in Swift -
import UIKit
class CustomView : UIView {
init() {
super.init(frame: UIScreen.mainScreen().bounds);
//for debug validation
self.backgroundColor = UIColor.blueColor();
print("My Custom Init");
return;
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}
Here is a full project with example:
UIView Example Project (with SubView example)
Here is how I do a Subview on iOS in Swift -
class CustomSubview : UIView {
init() {
super.init(frame: UIScreen.mainScreen().bounds);
let windowHeight : CGFloat = 150;
let windowWidth : CGFloat = 360;
self.backgroundColor = UIColor.whiteColor();
self.frame = CGRectMake(0, 0, windowWidth, windowHeight);
self.center = CGPoint(x: UIScreen.mainScreen().bounds.width/2, y: 375);
//for debug validation
self.backgroundColor = UIColor.grayColor();
print("My Custom Init");
return;
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}

Resources