Save styled element to reuse in other view controllers - ios

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.

Related

Trying to give UIButton a special property

I need UIButton to have the property bookId. I tried the following code but it's giving me the error Property 'self.bookId' not initialized at super.init call. I need the property to be able to query the database for that specific bookId when the button is clicked on.
import UIKit
class BookUIButton: UIButton {
var bookId: String
init(frame: CGRect, bookId: String) {
super.init(frame: frame);
self.bookId = bookId
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func awakeFromNib() {
super.awakeFromNib()
//TODO: Code for our button
}
}
Any help would be greatly appreciated!!!
swift enforces you to initialize every member var before it is ever/might ever be used. Since it can't be sure what happens when it is supers turn, it errors out: better safe than sorry!
Solution 1:
class BookUIButton: UIButton {
var bookId: String?
init(frame: CGRect, bookId: String) {
super.init(frame: frame);
self.bookId = bookId
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func awakeFromNib() {
super.awakeFromNib()
//TODO: Code for our button
}
}
Solution 2:
There is a better way to skip this error. So all you have to do is to initialize member after declaration:
class BookUIButton: UIButton {
var bookId: String = String()
init(frame: CGRect, bookId: String) {
super.init(frame: frame);
self.bookId = bookId
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func awakeFromNib() {
super.awakeFromNib()
//TODO: Code for our button
}
}
Is it important to have it named bookId? And is it important to be a string?
What if you just gave the UIButton a tag? (e.g. ButtonName.tag = Int)
Since you can always instantiate a button from storyboard, that button need to have a required init?(coder aDecoder: NSCoder). That method is required just for you to insert a button in the storyboard.
If the button is actually created via storyboard, than of course it won't have the id soon enough (at init time) for it not to be optional.
Since I assume that you will always instantiate the button programmatically, and not via storyboard, you can just implement your own init, with your params, and than, in the required init, just insert a fatalError.
That will compile as the fatalError returns Never (meaning it never returns), so the compiler can understand that bookId will never actually be nil.
Actually if you delete the required init Xcode will suggest you to insert the method with a fatalError and will autocomplete it for you if you want.
Of course, after that, if you put a button of this class in the storyboard, it will crash, so don't do that.
Adding a property to a button (or view in general) and force it to be created via some init and not via storyboard, in general, is perfectly fine if you don't intend to use the storyboard for that button (or view).
PS: after this brief on how to do what you want to do, I want to suggest you NOT to do it in THIS case, as it seems a bad idea for a button to hold any sort of data about some API you need to call. The button should just be a button and inform you when the user taps it. Then, when the button is pressed, some other class should handle what to do, using some other model to get the correspondent bookId for that button tap.
Completely different was if you would add some property that helped the button to look different, or add some other behavior specific to the use of the button (which is just be tappable and inform of taps)

UILabels as part of UIView subclass do not initiate

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

#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.

UIElements does not firing on view highrarchy

i added two custom view classes and the elements of that class are addede to viewcontroller class but i cannot accecssing 1st element but i can access second element
hear my code
1st class
class TextField: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
let view=UIView(frame : CGRectMake(8,100,270,47))
let headerText=UILabel(frame : CGRectMake(0,0,view.bounds.width,16))
headerText.text="label"
headerText.textColor=UIColor.blackColor()
headerText.textAlignment=NSTextAlignment.Left
headerText.font = UIFont.systemFontOfSize(13.0)
headerText.numberOfLines = 0
view.addSubview(headerText)
let textField=UITextField(frame : CGRectMake(0,20,view.bounds.width,28))
textField.borderStyle=UITextBorderStyle.RoundedRect
textField.userInteractionEnabled=true
textField.keyboardType=UIKeyboardType.NamePhonePad
view.addSubview(textField)
self.addSubview(view)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2nd class
protocol ElementDelegate {
func buttonClick(sender:UIButton)
}
class ButtonClass: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
let button=UIButton(frame: CGRectMake(10,50,270,30))
button.setTitle("Click", forState: UIControlState.Normal)
button.backgroundColor=UIColor.blueColor()
button.userInteractionEnabled=true
button.addTarget(self, action: #selector(button_click(_:)), forControlEvents: UIControlEvents.TouchUpInside)
self.addSubview(button)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func button_click(sender:UIButton){
delegate.buttonClick(sender)
}
}
my viewcontroller class
class ViewController: UIViewController,ElementDelegate {
override func viewDidLoad() {
let screenSize:CGRect=UIScreen.mainScreen().bounds
let date = TextField(frame: CGRectMake(0,10,screenSize.width,screenSize.height))
self.view.addSubview(date)
let element = Button(frame: CGRectMake(0,70,screenSize.width,screenSize.height))
element.delegate = self
self.view.addSubview(element)
}
}
when under running i added first textfield view second i added button view but when i click in textbox i cannot accessing it but button is working fine when i remove button textbox working fine why i cannot accessing both one after another
please help me.....
let date = TextField(frame: CGRectMake(0,10,screenSize.width,screenSize.height))
...
let element = Button(frame: CGRectMake(0,10,screenSize.width,screenSize.height))
You've created two subviews with exactly the same frame (size and position). The one you add second will be on top of the other, and will be taking the touch events, even if the button and textfield within them are in different places - a plain UIView will still take touch events.
Why are you making a view subclass that just holds another view inside it in a different position? Why not just position the button and textfield directly?

How to custom this image tab in UIView,using #IBDesignable and #IBInspectable is better

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

Resources