I have been creating a UILabel class called RPLabel, which is supposed to shorten all of my long list of programmatically set labels. This is the class code:
class RPLabel: UILabel {
// moneyTitle.frame = CGRect(x: 50, y: -50, width:XX, height: 35) //
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.setup()
self.testForLabelType()
}
enum labelTypeEnumeration {
case title, subtitle
}
var labelType = labelTypeEnumeration.title
override init(frame: CGRect){
super.init(frame: frame)
self.setup()
}
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
func setup(){
self.text = self.text
self.textColor = self.textColor
self.font = self.font
self.layer.display()
}
func testForLabelType() {
switch labelType {
case .title:
setupTitle()
print("setupTitle")
case .subtitle:
setupSubtitle()
print("setupSubtitle")
}
}
func setupTitle(){
self.font = UIFont.boldSystemFont(ofSize: 12)
self.textColor = UIColor.secondaryColor()
self.textAlignment = .left
}
func setupSubtitle(){
self.font = UIFont.boldSystemFont(ofSize: 18)
self.textColor = UIColor.rgb(red: 200, green: 200, blue: 200)
self.textAlignment = .left
}
}
To make a label, I use the following piece of code:
var moneyTitle = RPLabel()
moneyTitle.labelType = .title
moneyTitle.testForLabelType()
moneyTitle.text = "MONEY"
moneyTitle.frame = CGRect(x: 50, y: -50, width:XX, height: 35)
The issue that I have is that I would like to set the labelType to .title without having to write moneyTitle.testForLabelType(). In other words, I would like for that function to run automatically in the class. An alternative I would not like would be inserting the parameter in the init, so please avoid telling me to do so, thanks.
You can use didSet:
didSet is called immediately after the new value is stored.
var labelType = labelTypeEnumeration.title {
didSet {
testForLabelType()
}
}
Related
I customized a button and I wanted to make it designable in the storyboard.
If I set the background to an image which is stored in the assets folder, the storyboard doesn't show anything. I still get a transparent button. Any ideas how i can fix this?
import UIKit
#IBDesignable class AnswerButton: UIButton {
private static let BTN_NORMAL_IMAGE_NAME = "ButtonNormal"
private static let BTN_ANSWERED_IMAGE_NAME = "ButtonAnswered"
enum AnswerState : Int {
case normal = 0
case answered
case correctAnswered
case wrongAnswered
}
var answerState: AnswerState = .normal {
didSet {
if answerState == .normal {
self.answerLabel.textColor = UIColor.white
self.setBackgroundImage(UIImage(named: AnswerButton.BTN_NORMAL_IMAGE_NAME), for: .normal)
} else if answerState == .answered {
self.answerLabel.textColor = UIColor.black
self.setBackgroundImage(UIImage(named: AnswerButton.BTN_ANSWERED_IMAGE_NAME), for: .normal)
} else if answerState == .correctAnswered {
self.answerLabel.textColor = UIColor.blue
self.setBackgroundImage(UIImage(named: AnswerButton.BTN_ANSWERED_IMAGE_NAME), for: .normal)
}
}
}
#IBInspectable var myAnswerState: Int {
get {
return self.answerState.rawValue
}
set (value) {
self.setAnswerState(AnswerState(rawValue: value) ?? .normal)
}
}
#IBInspectable var letter: String = "" {
didSet {
self.letterLabel.text = String(letter.first!) + ":"
}
}
#IBInspectable var answer: String = "" {
didSet {
self.answerLabel.text = answer
}
}
public let letterLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 1
label.textAlignment = .left
label.textColor = UIColor.orange
label.font = UIFont(name: "Arial Rounded MT Bold", size: 24)
return label
}()
public let answerLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 1
label.textAlignment = .left
label.textColor = UIColor.white
label.font = UIFont(name: "Arial Rounded MT Bold", size: 22)
return label
}()
override func layoutSubviews() {
super.layoutSubviews()
self.letterLabel.frame = CGRect(x: 40, y: 0, width: 30, height: self.frame.height)
self.answerLabel.frame = CGRect(x: 80, y: 0, width: 310, height: self.frame.height)
}
private func setAnswerState(_ state: AnswerState) {
self.answerState = state
}
private func setLetter(_ letter: String) {
self.letter = letter
}
private func setAnswer(_ answer: String) {
self.answer = answer
}
init(letter: String, selectionState: AnswerState, answer: String) {
super.init(frame: CGRect(x: 0, y: 0, width: 420, height: 47))
self.sharedInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.sharedInit()
}
#objc
func touchUpInside(sender: UIButton) {
self.setAnswerState(.answered)
}
func sharedInit() {
self.adjustsImageWhenHighlighted = false
self.addTarget(self, action: #selector(touchUpInside), for: .touchUpInside)
self.setLetter(letter)
self.setAnswer(answer)
self.setAnswerState(.normal)
self.addSubview(self.letterLabel)
self.addSubview(self.answerLabel)
}
override func prepareForInterfaceBuilder() {
self.sharedInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.sharedInit()
}
}
an image with the name ButtonBackground lives in the assets folder.
You have a few issues in your code...
First, to get images to load at design-time / #IBDesignable, you need to tell Interface Builder where to get them:
let dynamicBundle = Bundle(for: AnswerButton.self)
let img = UIImage(named: AnswerButton.BTN_NORMAL_IMAGE_NAME, in: dynamicBundle, compatibleWith: nil)
Unless you're doing something out-of-the-ordinary with your bundle structures, you can use that both at design-time and run-time. That is, you don't conditional code.
Next, in your #IBInspectable var letter / didSet:
#IBInspectable var letter: String = "" {
didSet {
self.letterLabel.text = String(letter.first!) + ":"
}
}
make sure your string is not empty or it will crash:
#IBInspectable var letter: String = "" {
didSet {
if !letter.isEmpty {
self.letterLabel.text = String(letter.first!) + ":"
}
}
}
Next, your sharedInit() func will be called when you change an #IBInspectable value, so don't call this:
self.setAnswerState(.normal)
or your button will never reflect any other value.
And, the way you're setting frames for your labels looks problematic -- if the button width is less than 390-pts, the answerLabel won't fit.
Lastly, I'd suggest loading your two images at init, instead of re-loading every time the state changes.
Here's an update to your class:
#IBDesignable class AnswerButton: UIButton {
private static let BTN_NORMAL_IMAGE_NAME = "ButtonNormal"
private static let BTN_ANSWERED_IMAGE_NAME = "ButtonAnswered"
private var normalImage: UIImage!
private var answeredImage: UIImage!
enum AnswerState : Int {
case normal
case answered
case correctAnswered
case wrongAnswered
}
var answerState: AnswerState = .normal {
didSet {
switch answerState {
case .answered:
self.answerLabel.textColor = UIColor.black
self.setBackgroundImage(answeredImage, for: .normal)
case .correctAnswered:
self.answerLabel.textColor = UIColor.blue
self.setBackgroundImage(answeredImage, for: .normal)
case .wrongAnswered:
self.answerLabel.textColor = UIColor.red
self.setBackgroundImage(answeredImage, for: .normal)
default:
self.answerLabel.textColor = UIColor.white
self.setBackgroundImage(normalImage, for: .normal)
}
}
}
#IBInspectable var myAnswerState: Int {
get {
return self.answerState.rawValue
}
set (value) {
if let t: AnswerState = AnswerState(rawValue: value) {
self.setAnswerState(t)
} else {
self.setAnswerState(.normal)
}
}
}
#IBInspectable var letter: String = "" {
didSet {
if !letter.isEmpty {
self.letterLabel.text = String(letter.first!) + ":"
}
}
}
#IBInspectable var answer: String = "" {
didSet {
self.answerLabel.text = answer
}
}
public let letterLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 1
label.textAlignment = .left
label.textColor = UIColor.orange
label.font = UIFont(name: "Arial Rounded MT Bold", size: 24)
label.backgroundColor = UIColor.red.withAlphaComponent(0.25)
return label
}()
public let answerLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 1
label.textAlignment = .left
label.textColor = UIColor.white
label.font = UIFont(name: "Arial Rounded MT Bold", size: 22)
label.backgroundColor = UIColor.green.withAlphaComponent(0.25)
return label
}()
override func layoutSubviews() {
super.layoutSubviews()
self.letterLabel.frame = CGRect(x: 40, y: 0, width: 30, height: self.frame.height)
self.answerLabel.frame = CGRect(x: 80, y: 0, width: 310, height: self.frame.height)
}
private func setAnswerState(_ state: AnswerState) {
self.answerState = state
}
private func setLetter(_ letter: String) {
self.letter = letter
}
private func setAnswer(_ answer: String) {
self.answer = answer
}
init(letter: String, selectionState: AnswerState, answer: String) {
super.init(frame: CGRect(x: 0, y: 0, width: 420, height: 47))
setAnswer(answer)
self.sharedInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.sharedInit()
}
#objc
func touchUpInside(sender: UIButton) {
self.setAnswerState(.answered)
}
func sharedInit() {
self.adjustsImageWhenHighlighted = false
self.addTarget(self, action: #selector(touchUpInside), for: .touchUpInside)
self.setLetter(letter)
self.setAnswer(answer)
// don't call this here!
//self.setAnswerState(.normal)
self.addSubview(self.letterLabel)
self.addSubview(self.answerLabel)
// load normal / answered images once
let dynamicBundle = Bundle(for: AnswerButton.self)
if let img = UIImage(named: AnswerButton.BTN_NORMAL_IMAGE_NAME, in: dynamicBundle, compatibleWith: nil) {
self.normalImage = img
}
if let img = UIImage(named: AnswerButton.BTN_ANSWERED_IMAGE_NAME, in: dynamicBundle, compatibleWith: nil) {
self.answeredImage = img
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
self.sharedInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.sharedInit()
}
}
I've create a custom UIButton class to make it look like drop down selection. I've small down arrow image on the right of button. I want to change the button's image to different images such as grey, white or red depending on various conditions. How to do it? Following is my code:
class DropDownButton: UIButton {
let dropDownImageGrey = UIImage(named: "Icons/DropDown/Grey")
let dropDownImageWhite = UIImage(named: "Icons/DropDown/White")
let dropDownImageRed = UIImage(named: "Icons/DropDown/Red")
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: self.frame.width - 108, bottom: 0, right:0)
self.setImage(dropDownImageGrey, for: [])
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -13, bottom: 0, right:0)
}
}
override func viewDidLoad() {
// Change image to red one
dropDownButton.??? // How to change?
}
You may want to use setImage property of UIButton,
override func viewDidLoad() {
// Change image to red one
let dropDownButton = DropDownButton()
dropDownButton.setImage(dropDownButton.dropDownImageRed, for: .normal)
}
If the images are added in Assets.xcassets then you can use image literals or directly use the names like,
let dropDownImageGrey = UIImage(named: "Grey")
let dropDownImageWhite = UIImage(named: "White")
let dropDownImageRed = UIImage(named: "Red")
I think some answer above work. However, for the sake of production code, I suggest to use enum for the list of images.
class DropDownButton: UIButton {
enum ImageType: String {
case grey = "Icons/DropDown/Grey"
case white = "Icons/DropDown/White"
case red = "Icons/DropDown/Red"
var image: UIImage? { return UIImage(named: rawValue) }
}
var imageType: ImageType = .red {
didSet {
setImage(imageType.image, for: .normal)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: self.frame.width - 108, bottom: 0, right:0)
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -13, bottom: 0, right:0)
}
}
override func viewDidLoad() {
// Change image to red one
dropDownButton.imageType = .red
}
Later, if you need to change the image type, simply set the imageType of the button. Like
dropDownButton.imageType = .grey
dropDownButton.imageType = .white
You can use an enum to define the various condition and add a method to get images. Based on the condition you can set the image. Example as below:
Define this enum outside of your class
enum DropdownCondition
{
case condition1
case condition2
case condition3
func getImage() -> UIImage? {
switch self {
case .condition1:
return UIImage(named: "Icons/DropDown/Grey")
case .condition1:
return UIImage(named: "Icons/DropDown/White")
case .condition1:
return UIImage(named: "Icons/DropDown/Red")
default:
return nil
}
}
}
in your viewDidLoad/init or any method call yourMethodWithSomeoCndition(.condition1) based on the condition.
override func viewDidLoad() {
// Change image to red one
let dropDownButton = DropDownButton()
//Call based on your condition
yourMethodWithSomeoCndition(.condition1)//This condition can change on the fly
}
func yourMethodWithSomeoCndition(_ condition:DropdownCondition)
{
self.dropDownButton.setImage(condition.getImage(), for: .normal)
}
So I have a UIBarbuttonItem that I am currently designing based off of a layout that I have done.
import Foundation
import UIKit
class LocationManager: UIBarButtonItem {
var viewController: MainViewController?
lazy var customButton : UIButton = {
let customButton = UIButton(type: .system)
customButton.setImage(UIImage(named: "downArrow"), for: .normal)
customButton.imageEdgeInsets = UIEdgeInsetsMake(0, 20, 0, -10)
guard let customFont = UIFont(name: "NoirPro-SemiBold", size: 20) else {
fatalError("""
Failed to load the "CustomFont-Light" font.
Make sure the font file is included in the project and the font name is spelled correctly.
"""
)
}
customButton.semanticContentAttribute = UIApplication.shared
.userInterfaceLayoutDirection == .rightToLeft ? .forceLeftToRight : .forceRightToLeft
customButton.titleLabel?.font = customFont
customButton.setTitleColor(UIColor.black, for: .normal)
return customButton
}()
override init() {
super.init()
setupViews()
}
#objc func setupViews(){
customView = customButton
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I correctly do the job of using both an image and title and setting the image insets for the button and on load the appearance is great. However, when I leave the screen and come back it seems as though everything is thrown out of wack the image gets moved back and sometimes there will be two images and one will have a distorted size.
Is there anything wrong with my custom button implementation that I am missing.
I have included images for before and after
I suggest you to make your custom button class, then make title and image by adding subviews. In this case UIImageView and UILabel. Because UIButton inherits from UIView you can easy do this. I've never had problems using this way.
Here is the code I've written for you:
import UIKit
class ViewController: UIViewController {
lazy var customButton: CustomButton = {
let button = CustomButton(frame: CGRect(x: 50,
y: 200,
width: view.frame.width - 100,
height: 50))
// This mask for rotation
button.autoresizingMask = [.flexibleLeftMargin,
.flexibleRightMargin,
.flexibleTopMargin,
.flexibleBottomMargin]
button.attrTitleLabel.text = "San Francisco, CA"
button.addTarget(self, action: #selector(chooseCity), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
view.addSubview(customButton)
}
#objc func chooseCity() {
print("Choose city button has pressed")
}
}
class CustomButton: UIButton {
private let arrowImageSize: CGSize = CGSize(width: 20, height: 20)
private let sideOffset: CGFloat = 10
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
addSubview(attrTitleLabel)
addSubview(arrowImageView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var attrTitleLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "NoirPro-SemiBold", size: 20)
label.textColor = .black
return label
}()
lazy var arrowImageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "arrow_down")
iv.contentMode = .scaleAspectFit
return iv
}()
override func layoutSubviews() {
super.layoutSubviews()
arrowImageView.frame = CGRect(x: self.frame.width - arrowImageSize.width - sideOffset,
y: self.frame.height/2 - arrowImageSize.height/2,
width: arrowImageSize.width,
height: arrowImageSize.height)
attrTitleLabel.frame = CGRect(x: sideOffset, y: 0, width: self.frame.width - sideOffset*2 - arrowImageSize.width, height: self.frame.height)
}
}
How it looks:
I have made my first custom class in Swift and I want to use it for the text fields in my app. The problem is that it only works for the first textfield in the view controller, the class is not applied to the second one (the second one has no rounded corners, default placeholder color etc.). I have double checked that the right "custom class" is set in the storyboard for both textfields. Why isn't the class applied to both fields? Seems like a thing with a simple solution but I haven't found any...
Here is the class:
import UIKit
class RoundedUITextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
self.setAttributes()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setAttributes() {
self.layer.cornerRadius = 20.0
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: self.frame.height))
self.leftView = paddingView
self.leftViewMode = UITextFieldViewMode.always
self.textColor = UIColor.white
self.alpha = 0.7
let str = NSAttributedString(string: (self.placeholder)!, attributes: [NSForegroundColorAttributeName:UIColor.white])
self.attributedPlaceholder = str
}
}
Here is the view controller code:
class LogInViewController: UIViewController {
#IBOutlet weak var password: RoundedUITextField!
#IBOutlet weak var userName: RoundedUITextField!
override func viewDidLoad() {
super.viewDidLoad()
userName.setAttributes()
// Do any additional setup after loading the view.
}
I discovered now that the problem lies in the "setAttributes", I thought the attributes where set in the class but they're actually only set in viewDidLoad for one of them (I put it there in the beginning and forgot to remove it)... I want this to be done in the "init" of the class for all of the fields...
Put your self.setAttributes() in your awakeFromNib method and remove it from init(frame: CGRect)
override func awakeFromNib() {
super.awakeFromNib()
self.setAttributes()
}
your custom class code will be like this
import UIKit
class RoundedUITextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.setAttributes()
}
func setAttributes() {
self.layer.cornerRadius = 20.0
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: self.frame.height))
self.leftView = paddingView
self.leftViewMode = UITextFieldViewMode.always
self.textColor = UIColor.white
self.alpha = 0.7
let str = NSAttributedString(string: (self.placeholder)!, attributes: [NSForegroundColorAttributeName:UIColor.white])
self.attributedPlaceholder = str
}
}
Hope this helps
This is the code for my custom UILabel:
class RoleLabel: UILabel {
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
self.setup()
}
override init(frame:CGRect) {
super.init(frame:frame)
self.setup()
}
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
func setup(){
numberOfLines = 1
font = UIFont(name:"HelveticaNeue-Bold", size: 16.0)
sizeToFit()
frame = CGRectMake(frame.minX, frame.minY, frame.width + 40, frame.height+5)
backgroundColor = UIColor.blackColor()
textColor = UIColor.whiteColor()
layer.cornerRadius = 5
layer.shadowOffset = CGSize(width: 2, height: 2)
layer.shadowOpacity = 0.7
layer.shadowRadius = 2
drawTextInRect(frame)
let btn = UIButton()
btn.setTitle("X", forState: .Normal)
btn.setTitleColor(UIColor.whiteColor(), forState: .Normal)
btn.frame = CGRectMake(frame.width-20, 1, 20, 23)
addSubview(btn)
}
let topInset = CGFloat(0), bottomInset = CGFloat(0), leftInset = CGFloat(3), rightInset = CGFloat(0)
override func drawTextInRect(rect: CGRect) {
let insets: UIEdgeInsets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
}
However, since the label resizes according to the text, I need to pass the text as an argument to the initializer.
Is there a way to add this kind of custom parameters?
It doesn't let me add it in override init(frame:CGRect)
To override an init() method in swift you should use the keyword convenience.
As an example you can add the initializer:
convenience init(frame:frame, text:String) {
self.init(frame:frame)
self.text = text
}
Pay attention to the self.init(frame:frame) statement which initializes the UILabel by calling the default initializer.