Add subview in interfaceBuilder - ios

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()
}

Related

UITextView does not change textColor property

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

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.

#IBDesignable not showing background color in IB

I have a UIView as below:
import UIKit
#IBDesignable
class CHRAlertView: UIView {
#IBOutlet var icon:UILabel!
#IBOutlet var alertText:UITextView!
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
private func initialize(){
self.backgroundColor = UIColor.green
}
}
Based on how #IBDesignable works, this should show up in IB with a green background, but I get the clear color like this:
Why is this not functioning as expected? I need the background color to show in IB based on the defaults set in my #IBDesignable.
Since backgroundColor is an IB property not created via #IBInspectable, it always seems to overwrite whatever is in the init or draw methods. Meaning, if it is "default" in IB, it causes it to be overwritten with nil. However, if set in the prepareForInterfaceBuilder method backgroundColor works and shows in IB. So, the backgroundColor, it can be reasonably assumed, must be set at runtime. To do this I have the below:
//------------------
//Setup and initialization
//------------------
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
//Setups content, styles, and defaults for the view
private func initialize(){
self.staticContent()
self.initStyle()
}
//Sets static content for the view
private func staticContent() {
}
//Styles the view's colors, borders, etc at initialization
private func initStyle(){
}
//Styles the view for variables that must be set at runtime
private func runtimeStyle(){
if self.backgroundColor == nil {
self.backgroundColor = UIColor.green
}
}
override func prepareForInterfaceBuilder() {
self.runtimeStyle()
}
override func awakeFromNib() {
super.awakeFromNib()
self.runtimeStyle()
}
This defaults the backgroundColor if it is "default" (read nil) in IB to a color, but does not use the UIColor.green if a backgroundColor is set in IB, which is exactly what I need.
Shoutout to Eridius in the #swift-lang irc for helping me get to this answer.

Common init when using constants

Utilizing constants in subclasses with lot of initializers to override is tedious. Look at the class below, I need to duplicate the code in both initializers.
class Test : UIView {
let subview: UIView
override init(frame: CGRect) {
subview = UIView() // once
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
subview = UIView() // twice
super.init(coder: aDecoder)
}
}
If I try to make use of a common initializer then I get the following errors (see the comments)
override init(frame: CGRect) {
commonInit() // 1: Use of 'self' in method call 'commonInit' before super.init initializes self
super.init(frame: frame) // 2: Property 'self.subview' is not in initialized at super.init call
}
private func commonInit() {
subview = UIView() // 3: Cannot assign to 'subview' in 'self'
}
It works fine if I do not use a constant and define the subview like:
var subview: UIView?
And then of course switch order in init like this:
super.init(frame: frame)
commonInit()
So my question: is there no way to use a common initializer for constants in Swift as of now?
EDIT: I totally forgot to mention that the struggle here is that I can't initiate the subview before I'm in the init, it's initiated based on data that is not known when declaring the constant.
Try this:
class Test : UIView {
let subview = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Another option:
class Test : UIView {
let subview:UIView
init(frame: CGRect?, coder: NSCoder?) {
// The first phase initialization here
subview = UIView()
if let frame = frame {
super.init(frame: frame)
}
else if let coder = coder {
super.init(coder: coder)
}
else {
super.init()
}
// the Second phase initialization here
self.addSubview(subview)
}
convenience init() {
self.init(frame: nil, coder: nil)
}
override convenience init(frame: CGRect) {
self.init(frame: frame, coder: nil)
}
required convenience init(coder aDecoder: NSCoder) {
self.init(frame: nil, coder: aDecoder)
}
}
A little bit cleaner alternative:
class Test : UIView {
let subview:UIView
private enum SuperInitArg {
case Frame(CGRect), Coder(NSCoder), None
}
private init(_ arg: SuperInitArg) {
subview = UIView()
switch arg {
case .Frame(let frame): super.init(frame:frame)
case .Coder(let coder): super.init(coder:coder)
case .None: super.init()
}
addSubview(subview)
}
convenience init() {
self.init(.None)
}
override convenience init(frame: CGRect) {
self.init(.Frame(frame))
}
required convenience init(coder aDecoder: NSCoder) {
self.init(.Coder(aDecoder))
}
}
Do the following:
class Test : UIView {
let subview = UIView()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//edit subview properties as needed
}
}
This works well for me:
// Declare this somewhere (it can be used by multiple classes)
class FrameCoder: NSCoder {
let frame: CGRect
init(_ frame: CGRect) {
self.frame = frame
super.init()
}
}
Then, when you want a common initializer pattern, use this:
class MyView: UIView {
let something: SomeType
required init(coder aDecoder: NSCoder) {
if (aDecoder is FrameCoder) {
super.init(frame: (aDecoder as! FrameCoder).frame)
}
else {
super.init(coder: aDecoder)
}
// Common initializer code goes here...
something = // some value
}
override init(frame: CGRect) {
self.init(coder: FrameCoder(frame))
}
}
The advantage of using this method is you don't need to create default values for let definitions -- you can set them to the correct values in context, just like you would if there were only a single initializer.
Note that you can use this technique for initializers taking arbitrary values (not just for init(frame: CGRect)) -- you can create a specific NSCoder subclass to wrap any value and type you need to pass to an initializer, and then chain it into your init(coder:) method.
(also, there's probably some way to do this with a generic... haven't quite figured that out yet! Anyone...?)
One option is following the Xcode pattern:
class Test : UIView {
var subview: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
subview = UIView()
}
}
Notice your subview is a var
Another option is:
class Test : UIView {
let subview: UIView = {
let sv = UIView()
// some config, (i.e.: bgColor etc., frame is not yet _real_
// can't yet access instance's frame and other properties
return sv
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
// frame might be valid, preferably use layout constraints
addSubview(subview)
}
}
Hope this helps

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