I have customView called BaseView which has a contentView, In the contentView I am adding all other subviews(UILabel, UIButton, etc) in the override init(frame: CGRect) method.
Now I have 10 subclasses of my BaseView, which also overriding init(frame: CGRect) and calling base class init(frame: CGRect) method.
Here all my subclasses looks similar UI to its BaseView, Now there is one subclass of BaseView doesn't want some of the UI Elements in this base class, but I still need to call superview init(frame: CGRect). How do I change the code without affecting other classes?
Class BaseView: UIView {
let contentView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
let lbl1 = UILabel()
contentView.addSubView(lbl1)
let lbl2 = UILabel()
contentView.addSubView(lbl2)
self.addSubView(contentView)
}
Class subView1: BaseView {
override init(frame: CGRect) {
super.init(frame: frame)
// This class will show lbl1, lbl2 and lbl3 in the contentview
let lbl3 = UILabel()
contentView.addSubView(lbl3) // this contentview is BaseView's ContentView
}
// Similarly I have around 10 Subclasses of BaseView which is adding some UI Element to
baseview's contentView
// Question here is, below I am going to create another subclass of BaseView, But I don't
want to show lbl1 and lbl2 which is created in my BaseView's contentview
Class myView: BaseView {
// this class should not show the base class uilement lbl1 and lbl2, It should show only
lbl4 which is created by this class only
override init(frame: CGRect) {
super.init(frame: frame)
let lbl4 = UILabel()
contentView.addSubView(lbl4) // this contentview is BaseView's ContentView
}
How do I change the code without affecting other classes?
You don't. A different approach is needed. Three options are:
Don't derive the view in question from BaseView, and just reproduce whatever BaseView functionality you need.
Continue to derive from BaseView, and hide or remove the elements you don't need after BaseView's initialization method. The success of this plan will depend on how tolerant BaseView is when elements it expects are missing.
Refactor the functionality in BaseView into some new class that has the behavior that's common to all your views, and have your one outlier view and BaseView each descend from that new class.
Here is a different approach to implementing the required functionality.
Create a parent class with a content view where you want to show the content.
class SuperView: UIView {
let contentView = UIView()
override init(frame: CGRect) {
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
fatalError("init(coder:) has not been implemented")
}
}
Inherit BaseView class from SuperView class and you can inherit other classes from BaseView class.
class BaseView: SuperView {
override init(frame: CGRect) {
super.init(frame: .zero)
contentView.addSubview(UILabel()) // UILabel 1
contentView.addSubview(UILabel()) // UILabel 2
self.addSubview(contentView)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
fatalError("init(coder:) has not been implemented")
}
}
Inherit SubView class from SuperView class to add different properties and functions that you want. Here you don't need to inherit from BaseView class.
class SubView: SuperView {
override init(frame: CGRect) {
super.init(frame: .zero)
contentView.addSubview(UILabel()) // UILabel 3
self.addSubview(contentView)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
fatalError("init(coder:) has not been implemented")
}
}
I hope it will help you to implement the required functionality as you want.
Related
So, I have a subclass of UIView that I want to contain two custom UILabels. The subclass of my UIView initialises as "it should", however, the UILabels do not (I have tried with ordinary UILabels as well, but that does not work either.). The is no trace of any print statement and they are not displayed. They do work fine on their own, when I put one directly onto the storyboard.
I do not know were to turn, nor what is wrong. I have scoured the internet for days now. Please help this beginner...
class MainscreenButton: UIView {
#IBOutlet var icon: LAUILabel!
#IBOutlet var info: LAUILabel!
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)!
print("mainscreenbutton requiredinit")
}
override init(frame: CGRect) {
super.init(frame: frame)
print("mainscreenbutton frameinit")
}
}
Well.
they should initialize.. you just need to give them a frame/bounds/position. IBOutlet implies storyboard or NIB, so make sure that you set the class in the storyboard or nib and connect the IBOutlet. then give the labels constraints + text and they should appear.
if you wanted to do something like var icon = LAUILabel() and then in the initializers do like icon.frame = CGRect(... blah whatever) or programmatic constraints for autolayout that should work too
Do note that in my code though I have this as the storyboard/nib initializer
required init?(coder aDecoder: NSCoder) {
}
not sure if its just not picking up on it bc of slightly different method signature
you also many need to call label.layoutIfNeeded() if you don't give them explicit width constraints (maybe you only give it an x). Because it'll be initialized with a 0 width if theres no text and no width or no leading/trailing x constraints.
I'm not sure why you are doing UIView as button but if u want to do like that just flow this:
1) Create UILabel Custom class
class LAUILabel: UILabel {
//you can even define some params like #IBOutlet images ...
override init(frame: CGRect) {
super.init(frame: frame)
updateUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
updateUI()
}
private func updateUI() {
backgroundColor = .red
textColor = .green
numberOfLines = 1
textAlignment = .center
}
}
2) Create your MainscreenButton
class MainscreenButton: UIView {
#IBOutlet weak var icon: LAUILabel!
#IBOutlet weak var info: LAUILabel!
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)!
print("mainscreenbutton requiredinit")
}
override init(frame: CGRect) {
super.init(frame: frame)
print("mainscreenbutton frameinit")
}
}
3) Connect your UI with your custom classes
you will go to storyboard or Xib files and drag a view go to Identity Inspector then insert the name of your class 'MainscreenButton' in the custom class after that drag UILabels inside that view and change the custom class from the identity inspector like before 'LAUILabel' then link the the labels with the corespondent UI its should work.
4) Otherwise you can create the label without storyboard like this:
class MainscreenView: UIView {
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)!
print("mainscreenbutton requiredinit")
let icon = LAUILabel(frame: CGRect(origin: CGPoint(x: 100, y: 50), size: CGSize.zero))
icon.text = "icon"
icon.sizeToFit()
addSubview(icon)
let info = LAUILabel(frame: CGRect(origin: CGPoint(x: 200, y: 50), size: CGSize.zero))
info.text = "info"
info.sizeToFit()
addSubview(info)
}
override init(frame: CGRect) {
super.init(frame: frame)
print("mainscreenbutton frameinit")
}
}
I have a simple UIView subclass that looks like this:
import UIKit
class AngleViewManager: UIView {
class AngleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.red
}
init(first: CGPoint, second: CGPoint, third: CGPoint) {
// setup
super.init(frame: CGRect(dimensions))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}
When I initialize an AngleView instance within AngleViewManager like
AngleView(firstPoint, secondPoint, thirdPoint),
the background color is not red, and setting a breakpoint in the overridden init shows that it is never called even though I am explicitly calling it in my custom initializer which does get called successfully.
Am I missing something obvious?
You're calling the super class's (UIView) implementation of init(frame: CGRect).
Just change it to self.init(frame: frame)
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.
I'm making an iOS app with Xcode where all my buttons should have the same style. The only difference between these buttons are their height and width. Is there a way to save the first one I styled, and then use it again in the different view controllers, without copying? I'm thinking if this is possible it'll save me a lot of time.
Applying the same style to multiple instances of UIButton:
Strictly Programmatic route:
The first two methods are what I would do. The third is only to illustrate that it is possible to write an init that copies settings from another button.
Apply preset style with a sub class:
class StyledButton : UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.blackColor()
// more styling
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Apply preset style with an extension to UIButton:
extension UIButton {
func setCustomStyle1() {
self.backgroundColor = UIColor.blackColor()
// nore styling
}
}
Copy style with a convenience init in an extension to UIButton:
extension UIButton {
convenience init(styleFromButton button: UIButton, frame: CGRect) {
self.init(frame: frame)
self.backgroundColor = button.backgroundColor
}
}
Interface Builder solution:
Create a new Swift file:
Create a sub class of UIButton in the new file:
class StyledButton : UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
style()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
style()
}
private func style() {
self.backgroundColor = UIColor.blackColor()
// more styling
}
}
Go back to the Interface Builder and select a UIButton you want to style.
Select the third panel on the right, this is the identity inspector.
Select your sub class as the class for the UIButton.
Repeat for all buttons to style.
Or style the entire thing in IB and Alt-Drag to make a copy.
How to draw the following tab image in UIView?The text is changeable,which implies that the image could stretch in width. And I know in XCode6,it supports live render.So I think if possible,it's color,text,and size could be set in attributes inspector.
You have to create a custom class, based on UIView. This class is declared as #IBDesignable and has #IBInspectable properties. Override UIView.drawRect() and you are totally free on how your view gets displayed.
Here is a sample class to get you started.
import UIKit
#IBDesignable
class MyTabView: UIView {
#IBInspectable tabTitle: String = ""
#IBInspectable tabColor: UIColor = UIColor.clearColor()
override init(frame: CGRect) {
super.init(frame: frame)
// Initialization code
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func prepareForInterfaceBuilder() {
// stuff for interface builder only
}
override func drawRect(rect: CGRect)
{
// this is where your view gets drawed
self.layer.cornerRadius = 10
self.layer.masksToBounds = true
}
}