Introduction
I created a button and added subviews to it / gave it a custom look.
As subclassing buttons is a lot of pain, I decided to do it (yeah, I know it's bad) inside my view controller and creating an extension for UIButton which styles create buttons.
The button looks like this:
I set up the subviews as follows:
func styleCreateButton(serviceFee: Float) {
let feeLabel: DefaultLabel = {
let lbl = DefaultLabel(labelFont: UIFont(name: "MyCustomFont", size: 12)!, labelTextColor: ColorCodes.textSuperLightGray, labelText: "$\(serviceFee) Service Fee")
return lbl
}()
let createLabel: DefaultLabel = {
let lbl = DefaultLabel(labelFont: UIFont(name: "MyCustomFont", size: 15)!, labelTextColor: .white, labelText: "Create")
return lbl
}()
let arrowRight: UIButton = {
let btn = UIButton(type: .system)
btn.setImage(UIImage(named: "left-arrow")?.rotate(radians: CGFloat(180).degreesToRadians).resizedImage(newSize: CGSize(width: 20, height: 20)).withRenderingMode(.alwaysTemplate), for: .normal)
btn.contentMode = .center
btn.tintColor = .white
return btn
}()
self.addSubview(feeLabel)
feeLabel.anchor(top: nil, left: self.leftAnchor, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 16, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
feeLabel.sizeToFit()
feeLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
self.addSubview(arrowRight)
arrowRight.anchor(top: nil, left: nil, bottom: nil, right: self.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 16, width: 20, height: 20)
arrowRight.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
self.addSubview(createLabel)
createLabel.anchor(top: nil, left: nil, bottom: nil, right: arrowRight.leftAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 0, height: 0)
createLabel.sizeToFit()
createLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
The button itself was declared as follows:
private lazy var createButton: UIButton = {
let btn = UIButton(type: .system)
btn.backgroundColor = ColorCodes.logoPrimaryColor
btn.layer.shadowOffset = CGSize(width: 0, height: 5)
btn.layer.shadowColor = UIColor.black.cgColor
btn.layer.shadowOpacity = 0.2
btn.layer.shadowRadius = 5
return btn
}()
Also, the button has a target and I have no idea why it does not highlight in the typical iOS way when clicking. Right now, the handler function gets called, but the button does not highlight.
I would appreciate any help :)
try to do this
class HighlightedButton: UIButton {
override var isHighlighted: Bool {
didSet {
backgroundColor = isHighlighted ? .blue : .white
}
}
}
Related
Once my UIButton is pressed, I want to change the text of my UILabels. Right now when the button is pressed it transitions to a new ViewController with the same background and same theme, just with different text. I figure it is not the best approach. I cannot access my UILables outside of my function setUpView(). What is the best approach to do this.
#objc func buttonTapped(_ sender: UIButton){
//this is where I will change my text. myLabel.text = "new text"
let modalViewController = PageTwoViewController()
modalViewController.modalPresentationStyle = .overCurrentContext
present(modalViewController, animated: false)
}
func setUpView(){
let headerView = UIView()
headerView.backgroundColor = UIColor.rgb(red: 235, green: 248, blue: 242)
view.addSubview(headerView)
headerView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 150, paddingLeft: 20, paddingBottom: 150, paddingRight: 20, width: 0, height: 0)
let myLabel = UILabel()
myLabel.text = "Rate your xxxxx"
myLabel.textColor = UIColor.rgb(red: 0, green: 48, blue: 51)
myLabel.textAlignment = .left
myLabel.numberOfLines = 0
headerView.addSubview(myLabel)
myLabel.font = .systemFont(ofSize: 32)
myLabel.anchor(top: headerView.topAnchor, left: headerView.leftAnchor, bottom: nil, right: headerView.rightAnchor, paddingTop: 150, paddingLeft: 30, paddingBottom: 0, paddingRight: 40, width: 0, height: 0)
let steps = UILabel()
steps.text = "1 of 3"
steps.textColor = UIColor.rgb(red: 0, green: 48, blue: 51)
headerView.addSubview(steps)
steps.font = .systemFont(ofSize: 14)
headerView.addSubview(steps)
steps.anchor(top: nil, left: headerView.leftAnchor, bottom: myLabel.topAnchor, right: nil, paddingTop: 0, paddingLeft: 30, paddingBottom: 10, paddingRight: 0, width: 0, height: 0)
let button = UIButton(type: .system)
button.setTitle("Next", for: .normal)
button.layer.cornerRadius = 30
button.backgroundColor = UIColor.white
button.setTitleColor(UIColor.rgb(red: 0, green: 48, blue: 51), for: .normal)
button.layer.borderWidth = 1.7
button.layer.borderColor = UIColor.black.cgColor
button.titleLabel?.font = UIFont.systemFont(ofSize: 20)
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
headerView.addSubview(button)
button.anchor(top: nil, left: headerView.leftAnchor, bottom: headerView.bottomAnchor, right: headerView.rightAnchor, paddingTop: 0, paddingLeft: 25, paddingBottom: 30, paddingRight: 25, width: 0, height: 60)
}
override func viewDidLoad() {
setUpView()
super.viewDidLoad()
}
You have to declare the variables globally and then change the text.
class TestViewController: UIViewController {
private var myButton : UIButton = {
let button = UIButton()
button.backgroundColor = .red
return button
}()
private var myLabel: UILabel = {
let label = UILabel()
return label
}()
func setupView() {
myButton.setTitle("My Button", .normal) //You can do it like this
myLabel.text = "This is my text"
}
}
You can declare your labels globally in your view controller and then you can use any where in view controller as follow:
let myLabel: UILabel = {
let label = UILabel()
return label
}()
class ListCell: UICollectionViewCell {
let snapshotTitle: UILabel = {
let label = UILabel()
label.attributes(text: "Title", textColor: .white, alignment: .left, font: Fonts.rubikMedium, size: 15, characterSpacing: -0.05, backgroundColor: nil)
return label
}()
let snapshotOne: UILabel = {
let label = UILabel()
label.attributes(text: "Item 1", textColor: Colors.appDarkGrey, alignment: .left, font: Fonts.rubikRegular, size: 12, characterSpacing: -0.04, backgroundColor: nil)
return label
}()
let snapshotTwo: UILabel = {
let label = UILabel()
label.attributes(text: "Item 2", textColor: Colors.appDarkGrey, alignment: .left, font: Fonts.rubikRegular, size: 12, characterSpacing: -0.04, backgroundColor: nil)
return label
}()
let snapshotThree: UILabel = {
let label = UILabel()
label.attributes(text: "Item 3", textColor: Colors.appDarkGrey, alignment: .left, font: Fonts.rubikRegular, size: 12, characterSpacing: -0.04, backgroundColor: nil)
return label
}()
let snapshotFour: UILabel = {
let label = UILabel()
label.attributes(text: "Item 4", textColor: Colors.appDarkGrey, alignment: .left, font: Fonts.rubikRegular, size: 12, characterSpacing: -0.04, backgroundColor: nil)
return label
}()
let snapshotContainer: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 12
return view
}()
let snapshotGradientBackground: UIImageView = {
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "itemCellBackgroundPink")
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 12
imageView.clipsToBounds = true
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(snapshotGradientBackground)
snapshotGradientBackground.setAnchors(top: contentView.topAnchor, paddingTop: 0, bottom: contentView.bottomAnchor, paddingBottom: 0, left: contentView.leftAnchor, paddingLeft: 0, right: contentView.rightAnchor, paddingRight: 0, width: 0, height: 0)
contentView.addSubview(snapshotContainer)
snapshotContainer.setAnchors(top: contentView.topAnchor, paddingTop: 45, bottom: contentView.bottomAnchor, paddingBottom: 0, left: contentView.leftAnchor, paddingLeft: 0, right: contentView.rightAnchor, paddingRight: 0, width: 0, height: 0)
contentView.addSubview(snapshotTitle)
snapshotTitle.setAnchors(top: contentView.topAnchor, paddingTop: 0, bottom: snapshotContainer.topAnchor, paddingBottom: 0, left: contentView.leftAnchor, paddingLeft: 12, right: contentView.rightAnchor, paddingRight: 12, width: 0, height: 0)
contentView.addSubview(snapshotOne)
snapshotOne.setAnchors(top: snapshotContainer.topAnchor, paddingTop: 18, bottom: nil, paddingBottom: 0, left: snapshotContainer.leftAnchor, paddingLeft: 12, right: snapshotContainer.rightAnchor, paddingRight: 12, width: 0, height: 14)
contentView.addSubview(snapshotTwo)
snapshotTwo.setAnchors(top: snapshotOne.bottomAnchor, paddingTop: 14, bottom: nil, paddingBottom: 0, left: snapshotContainer.leftAnchor, paddingLeft: 12, right: snapshotContainer.rightAnchor, paddingRight: 12, width: 0, height: 14)
contentView.addSubview(snapshotThree)
snapshotThree.setAnchors(top: snapshotTwo.bottomAnchor, paddingTop: 14, bottom: nil, paddingBottom: 0, left: snapshotContainer.leftAnchor, paddingLeft: 12, right: snapshotContainer.rightAnchor, paddingRight: 12, width: 0, height: 14)
contentView.addSubview(snapshotFour)
snapshotFour.setAnchors(top: snapshotThree.bottomAnchor, paddingTop: 14, bottom: nil, paddingBottom: 0, left: snapshotContainer.leftAnchor, paddingLeft: 12, right: snapshotContainer.rightAnchor, paddingRight: 12, width: 0, height: 14)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I am using a completely programmatic approach to this app
To help understand this code better, I used extensions for UILabel and UIView:
".attributes" specifies text, text color, text alignment, font, text size, and background color
".setAnchors" specifies autolayout constraints
Instead of adding each of these labels (snapshotOne, snapshotTwo, snapshotThree, snapshotFour) manually, how would I iterate over a list of items and set the label to that item? Currently, I am just creating separate closures for each item and then adding them in one by one. If it helps, the maximum amount of items I want to show is four (first four items in the list of items).
Here is an image to show what the current code does:
This is a huge question that I have, and any help is very much appreciated. I am fairly new to Swift programming, so feel free to point out any other concerns in my code if you see any. Thank you!
For the 4 snapshot labels, you can put them in an array:
let snapshotLabels = (1...4).map { number in
let label = UILabel()
label.attributes(text: "Item \(number)", // note the use of "number" here
textColor: Colors.appDarkGrey,
alignment: .left,
font: Fonts.rubikRegular,
size: 12,
characterSpacing: -0.04,
backgroundColor: nil)
return label
}
For the layout, you could use a UIStackView or a UITableView, depending on how you want it to look when the number of items is fewer than 4.
With UIStackView, you could do
let stackView = UIStackView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
stackView.setAnchors(...)
stackView.axis = .vertical
stackView.alignment = .leading
stackView.distribution = .fillEqually
for label in snapshotLabels {
stackView.addArrangedSubview(label)
}
I have created 5 custom buttons all in code, and they all show up perfectly; however, for some reason, there is no response when tapped like they are inactive. I have added the subviews in my ViewDidLoad and coded the constraints in a separate function that's not important I don't believe to this so let me know if that's needed to better your understanding.
The code:
let backgroundImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.masksToBounds = true
return imageView
}()
let transparentView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black
view.backgroundColor = view.backgroundColor?.withAlphaComponent(0.5)
return view
}()
let button1: UIButton = {
let button = UIButton(type: .custom)
button.addTarget(self, action: #selector(button1Pressed), for: .touchUpInside)
button.setImage(UIImage(named: "button"), for: .normal)
button.isUserInteractionEnabled = true
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
addSubviews()
setupViewConstraints()
}
func addSubviews() {
self.view.addSubview(backgroundImageView)
backgroundImageView.addSubview(transparentView)
transparentView.addSubview(button1)
}
func setupViewConstraints() {
backgroundImageView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
transparentView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
button1.anchor(top: nil, left: nil, bottom: view.bottomAnchor, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 20, paddingRight: 0, width: 80, height: 80)
button1.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
#objc func button1Pressed() {
print("Button1 pressed")
}
By default, user interaction is disabled in UIImageView instances. And when a superview's user interaction is disabled, its subviews can't receive the user interaction, even if you have set button.isUserInteractionEnabled = true
So add imageView.isUserInteractionEnabled = true in backgroundImageView closure
let backgroundImageView: UIImageView = {
let imageView = UIImageView()
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.masksToBounds = true
return imageView
}()
Can someone tell my why this code does not autosize the text in the descLabel? I tried to set the numberOfLines to 0 and I set a lineBreakMode but this pretty much does nothing to solve my problem. Is this approach not possible in a ViewController or do I do something completely wrong?
class SingleEventViewController: UIViewController {
var thisEvent: Event
var eventDescription: String?
let descLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.text = "Beschreibung"
label.textColor = .black
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
return label
}()
let participateButton: UIButton = {
let button = UIButton()
button.backgroundColor = CalendarSettings.Colors.buttonBG
button.setTitle("Teilnehmen", for: .normal)
button.setTitleColor(CalendarSettings.Colors.darkRed, for: .normal)
return button
}()
//MARK: - Init & View Loading
init(event: Event) {
thisEvent = event
super.init(nibName: nil, bundle: nil)
setupDefaultValues()
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
applyDefaultValues()
setupViews()
confBounds()
}
//MARK: - Setup
func setupDefaultValues() {
eventDescription = thisEvent.eventDescription
}
func applyDefaultValues() {
descLabel.text = eventDescription
}
func setupViews() {
view.addSubview(descLabel)
view.addSubview(participateButton)
let tabbarHeight = self.tabBarController?.tabBar.frame.height ?? 0
descLabel.anchor(top: titleLabel.bottomAnchor, left: view.leftAnchor, bottom: nil, right: nil, paddingTop: 5, paddingLeft: 10, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
participateButton.anchor(top: nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: tabbarHeight, paddingRight: 0, width: 0, height: 50)
}
To make it autoresize you must give it a left/leading and right/trailing constraints , or a fixed width
descLabel.anchor(top: titleLabel.bottomAnchor, left: view.leftAnchor, bottom: nil, rightview.rightAnchor, paddingTop: 5, paddingLeft: 10, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
If it is the font size you are trying to change so that it fits in the label then try adjustFontSizeToWidth, but if it is the label you wish to resize try sizeToFit().
I've looked at almost every stackoverflow solution and for some odd reason my button will not round the corners. Can someone check and see what im doing wrong?
let goToMapsButton = UIButton(type: .custom)
scrollView.addSubview(goToMapsButton)
_ = goToMapsButton.anchor(map.bottomAnchor, left: nil, bottom: seperator.topAnchor, right: self.view.rightAnchor, topConstant: 16, leftConstant: 0, bottomConstant: 16, rightConstant: 16, widthConstant: 50, heightConstant: 50)
goToMapsButton.backgroundColor = .green
goToMapsButton.layer.cornerRadius = 0.5 * goToMapsButton.bounds.size.width
goToMapsButton.clipsToBounds = true
goToMapsButton.layer.masksToBounds = true
Btw, Im doing this all in the viewDidLoad section of the view controller, if that info makes a difference.
Here is the full viewDidLoadClass for reference:` override func viewDidLoad() {
super.viewDidLoad()
let map = MKMapView()
let view1 = UIView()
view1.backgroundColor = .red
let storeAddress = UILabel()
storeAddress.text = "318 Atwood Avenue"
storeAddress.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.medium)
let storeCity = UILabel()
storeCity.text = "Rainy River"
storeCity.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.medium)
let seperator = UIView()
seperator.backgroundColor = .lightGray
let goToMapsButton = UIButton(type: .custom)
scrollView.addSubview(map)
scrollView.addSubview(view1)
scrollView.addSubview(storeAddress)
scrollView.addSubview(storeCity)
scrollView.addSubview(goToMapsButton)
scrollView.addSubview(seperator)
map.anchorToTop(scrollView.topAnchor, left: self.view.leftAnchor, bottom: nil, right: self.view.rightAnchor)
map.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.6).isActive = true
_ = storeAddress.anchor(map.bottomAnchor, left: self.view.leftAnchor, bottom: nil, right: nil, topConstant: 16, leftConstant: 16, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
_ = storeCity.anchor(storeAddress.bottomAnchor, left: self.view.leftAnchor, bottom: nil, right: nil, topConstant: 8, leftConstant: 16, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
_ = goToMapsButton.anchor(map.bottomAnchor, left: nil, bottom: nil, right: self.view.rightAnchor, topConstant: 16, leftConstant: 0, bottomConstant: 16, rightConstant: 16, widthConstant: 50, heightConstant: 50)
goToMapsButton.backgroundColor = .green
print(goToMapsButton.frame.width)
goToMapsButton.layer.cornerRadius = 0.25 * goToMapsButton.frame.width
goToMapsButton.clipsToBounds = true
goToMapsButton.layer.masksToBounds = true
_ = seperator.anchor(storeCity.bottomAnchor, left: self.view.leftAnchor, bottom: nil, right: self.view.rightAnchor, topConstant: 8, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 1)
view1.anchorToTop(map.bottomAnchor, left: self.view.leftAnchor, bottom: scrollView.bottomAnchor, right: self.view.rightAnchor)
view1.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.8).isActive = true
}`
Move this code in viewWillLayoutSubviews:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
goToMapsButton.layer.cornerRadius = 0.25 * goToMapsButton.frame.width
}
Or create your custom class for button with rounded corners:
class RoundedButton: UIButton {
#IBInspectable var cornerRadius: CGFloat = 0
override func layoutSubviews() {
super.layoutSubviews()
clipsToBounds = true
layer.cornerRadius = cornerRadius
}
}
I face the same issue today, I'm using Anchor too. Well it looks like a one line stuff to me, never think it would takes that much of code.
The difficult is that we cannot get the frame.height or width in UIButton property closure because autolayout has not calculate the size at that time. I do a test to apply the purely round corner in viewDidLayoutSubviews.
And this is what it is looks like, although the declaration is separated for those UIButtons. I really don't like this way, I want to put all button declaration code in the same place, like a closure. However I don't find any other way to do it in internet.
let b1 = UIButton()
let b2 = UIButton()
let b3 = UIButton()
let b4 = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
configureLayout()
}
private func configureLayout() {
// disable large title
navigationItem.largeTitleDisplayMode = .never
let topLabel = UILabel()
topLabel.text = "Import your music"
topLabel.font = .systemFont(ofSize: 30, weight: .heavy)
topLabel.numberOfLines = 0
topLabel.textColor = .black
topLabel.textAlignment = .center
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
stack.spacing = 40
stack.addArrangedSubview(topLabel)
b1.setTitle("Local drive", for: .normal)
b2.setTitle("Google", for: .normal)
b3.setTitle("OneNote", for: .normal)
b4.setTitle("DropBox", for: .normal)
b1.backgroundColor = .myBlue
b2.backgroundColor = .myGreen
b3.backgroundColor = .systemPink
b4.backgroundColor = .myPuple
b1.tag = 1
b2.tag = 2
b3.tag = 3
b4.tag = 4
[b1, b2, b3, b4].forEach { b in
b.titleLabel?.font = .systemFont(ofSize: 20, weight: .bold)
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.gray, for: .highlighted)
stack.addArrangedSubview(b)
b.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
view.addSubview(stack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: g.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: g.centerYAnchor),
stack.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.75)
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
[b1, b2, b3, b4].forEach { b in
b.layer.cornerRadius = b1.frame.height / 2
b.layer.cornerCurve = .continuous
}
}
Give your button a frame. like that
goToMapsButton.frame = CGRect(x: xposition, y:yposition, width: widthyouwant, height: heightyouwant)
as currently your size of button is zero 0*0.5 = zero thats why its not applying any radius.
give it frame and it will work...
UIButton might have a background UIImage with rounded corners. It allowes you to set a background UIImage for each UIControlState of your UIButton.
open class UIButton : UIControl, NSCoding {
open func setBackgroundImage(_ image: UIImage?, for state: UIControlState)
}
If your UIButton's size is determined at runtime and the radius is fixed - you can use a resizable image:
open class UIImage : NSObject, NSSecureCoding {
open func resizableImage(withCapInsets capInsets: UIEdgeInsets) -> UIImage
}
On the gif below I use UIImage with size = CGSize(width: 7, height: 7), corner radius = 3 and cap insets = UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3)