UILabel Not Showing Up On View - swift

I just started learning how to code without using storyboards programatically, so please treat me as a beginner... Even though my image shows up on the screen, the label doesn't... I am a little confused as to why this is going on and I appreciate your help. Again, I'm a beginner, do don't mind if its a silly mistake!!!
import UIKit
class LocationRequestController : UIViewController
{
let imageView: UIImageView =
{
let iconImageView = UIImageView()
iconImageView.contentMode = .scaleAspectFit
iconImageView.image = UIImage(named: "blue-pin")
return iconImageView
}()
let allowLocationLabel : UILabel =
{
let label = UILabel()
let attributedText = NSMutableAttributedString(string: "Allow Location\n", attributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 24)])
attributedText.append(NSAttributedString(string: "Please enable location services For The Map To Work!", attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]))
label.numberOfLines = 0
label.textAlignment = .center
label.attributedText = attributedText
return label
}()
override func viewDidLoad()
{
super.viewDidLoad()
configureViewAppearance()
}
func configureViewAppearance()
{
view.backgroundColor = .white
view.addSubview(imageView)
imageView.anchor(top: view.topAnchor, left: nil, bottom: nil, right: nil, paddingTop: 140, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 200, height: 200)
imageView.centerX(inView: view)
view.addSubview(allowLocationLabel)
allowLocationLabel.anchor(top: imageView.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 32, paddingLeft: 32, paddingBottom: 0, paddingRight: 32, width: 0, height: 0)
allowLocationLabel.centerX(inView: view)
}
}
Thanks again!

You just need to add one line label.translatesAutoresizingMaskIntoConstraints
let allowLocationLabel : UILabel =
{
let label = UILabel()
let attributedText = NSMutableAttributedString(string: "Allow Location\n", attributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 24)])
attributedText.append(NSAttributedString(string: "Please enable location services For The Map To Work!", attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]))
label.numberOfLines = 0
label.textAlignment = .center
label.attributedText = attributedText
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()

This was the problem:
The text color was automatically set to .white, so that's the reason why it didn't seem to show up! Thanks for your help everyone!

Related

UILabel disappears in StackView when there is too much content

I have a UIStackView with three subviews in it: a title (UILabel), a body (UILabel), and a post image (UIImageView). This last image only gets added when the post has an imageURL (this is optional.)
Looking below, you can see that when I display the image, the UILabel disappears from out of the stackView somehow. How do I fix this?
P.S. Looking ahead, I am going to want to remove imageViews when the user is scrolling from those posts that lack imageViewURLs. Any tips on how to proceed here? Thank you again in advance.
Below is the relevant code:
class FeedTableViewCell: UITableViewCell {
//MARK: Public properties
var post: Post?{
didSet{
guard let post = post else {return}
// Adding user's name
let attributedText = NSMutableAttributedString(string: post.author.name + " → " + post.group.name, attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 14)])
// Adding date and user's first name
let dateFormatter = PostDateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
attributedText.append(NSAttributedString(string: "\n" + dateFormatter.string(from: post.createdAt), attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12), NSAttributedString.Key.foregroundColor: UIColor(r: 155/255, g: 161/255, b: 171/255)]))
// Increasing Spacing
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 4
attributedText.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, attributedText.length))
titleLabel.attributedText = attributedText
// Setting profile image
iconImageView.setImage(for: post.author, setContentMode: .scaleAspectFit)
messageTextView.text = post.content
setupImageSubviews()
}
}
//MARK: Private implementation
private let iconImageView = CircleImageView(size: 44)
private let titleLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 14)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let messageTextView: UILabel = {
let labelView = UILabel()
labelView.numberOfLines = 0
labelView.font = UIFont.systemFont(ofSize: 14)
labelView.translatesAutoresizingMaskIntoConstraints = false
return labelView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupDefaultViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var stackView = UIStackView()
func setupDefaultViews(){
backgroundColor = UIColor.white
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(messageTextView)
contentView.addSubview(iconImageView)
contentView.addSubview(stackView)
iconImageView.anchor(top: contentView.topAnchor, leading: contentView.leadingAnchor, bottom: nil, trailing: nil, padding: .init(top: 0, left: 8, bottom: 0, right: 0), size: CGSize(width: 44, height: 44))
stackView.anchor(top: contentView.topAnchor, leading: iconImageView.trailingAnchor, bottom: contentView.bottomAnchor, trailing: contentView.trailingAnchor, padding: .init(top: 0, left: 8, bottom: 8, right: 8))
stackView.axis = .vertical
}
private func setupImageSubviews() {
guard let imageURL = post?.imageURL else {return} // if no image exists, return, preventing image view from taking extra memory and performance to initialize and calculate constraints
// initialize here instead of globally, so it doesnt take extra memory holding this when no image exists.
let messageImageView: UIImageView = {
let imageView = UIImageView()
let contentImage = UIImage(systemName: "person.crop.circle.fill")!.withTintColor(.gray).withRenderingMode(.alwaysOriginal)
imageView.kf.setImage(with: imageURL, placeholder: contentImage)
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
imageView.layer.borderWidth = 1
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
stackView.addArrangedSubview(messageImageView)
}}
Just so people know how I fixed this, I was attempting to use one cell template which would handle two different kinds of cells. That is a mistake, instead use the information in this link Using Auto Layout in UITableView for dynamic cell layouts & variable row heights to properly create two cell templates. I also found this code guaranteed that the cells were properly displaying the titles:
// Keeps the title text always showing
override func didMoveToSuperview() {
super.didMoveToSuperview()
layoutIfNeeded()
}

retrieve information from firebase and set as an image and label in UICollectionView

I am trying to retrieve some data that Is stored in firebase, and allow that Information to be displayed inside of a collection view. Such as profileImageUrl, name label and email label.
I have tried to read the documentation on firebase and apple website, but for some reason, my code returns plain. I don't receive any errors, but my code is not running as expected. When I go to my app's profile view, the only texts that are displayed are the placeholder that I programmatically placed.
let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "users")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
return imageView
}()
let nameLabel: UILabel = {
let label = UILabel()
label.text = "User's Name"
label.font = UIFont.boldSystemFont(ofSize: 18)
label.textColor = GREEN_Theme
return label
}()
let uidLabel: UILabel = {
let label = UILabel()
label.text = "User's uid"
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = GREEN_Theme
return label
}()
let emailLabel: UILabel = {
let label = UILabel()
label.text = "User's email"
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = GREEN_Theme
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
func setupView() {
if Auth.auth().currentUser != nil {
guard let uid = Auth.auth().currentUser?.uid else {
return }
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String : Any] else {
return }
let user = CurrentUser(uid: uid, dictionary: dictionary)
self.uidLabel.text = uid
self.nameLabel.text = user.name
self.emailLabel.text = user.email
self.profileImageView.loadImageUsingCacheWithUrlString(user.profileImageUrl)
}, withCancel: { (err) in
print("attempting to load information")
})
self.addSubview(profileImageView)
self.addSubview(nameLabel)
self.addSubview(emailLabel)
self.addSubview(uidLabel)
profileImageView.anchors(top: topAnchor, topPad: 125, bottom: bottomAnchor, bottomPad: 75, left: leftAnchor, leftPad: 20, right: rightAnchor, rightPad: 250, height: 20, width: 20)
nameLabel.anchors(top: profileImageView.bottomAnchor, topPad: -50, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 0, height: 20, width: 20)
emailLabel.anchors(top: nameLabel.bottomAnchor, topPad: -80, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 125, height: 20, width: 20)
uidLabel.anchors(top: emailLabel.bottomAnchor, topPad: -40, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 0, height: 20, width: 20)
profileImageView.layer.zPosition = 10
nameLabel.layer.zPosition = 10
emailLabel.layer.zPosition = 10
profileImageView.layer.borderWidth = 2.0
profileImageView.layer.cornerRadius = 50
profileImageView.layer.borderWidth = 2.0
profileImageView.layer.borderColor = UIColor.white.cgColor
profileImageView.layer.masksToBounds = true
}
}
I would really appreciate it if someone could help critique what I have, as well as help show me what the correct implementation would look like. Any and all help would be greatly appreciated
There are several things that are not correct.
your cell should not be the one in charge of downloading or doing any network call, leave that to the viewController or even better a separate handler for all the network calls.
Firebase is asynchronous so by the time your data is downloaded all the views have already been set and displayed, that is why you are only seeing your placeholders.
You have a lot of redundant code for checking optionals, this is not much of a problem but it is not needed.
You should really think about changing all this code and separating the different tasks to different handlers. For example, you could create a separate class that controls all the calls to Firebase and returns the values with completion blocks, from the viewController or the collectionView itself ask the network handler to give you the data that you need, pass this data to the cell and let the cell display the data, the cell should only display the data, not download it or ask for networks requests. I cannot write up a complete example for you but I think that if you go around in google you will find a lot of very good examples and tutorials.
Anyway, just to get you started and so you have a look at the mistakes that you have in your code, here is a quick fix for it. Even if it works, don't take for granted that everything is fixed. You should really refactor all the code and separate the different tasks. Hope this helps at least so you can solve the problem of displaying your downloaded data in the cell.
let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "users")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
return imageView
}()
let nameLabel: UILabel = {
let label = UILabel()
label.text = "User's Name"
label.font = UIFont.boldSystemFont(ofSize: 18)
label.textColor = GREEN_Theme
return label
}()
let uidLabel: UILabel = {
let label = UILabel()
label.text = "User's uid"
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = GREEN_Theme
return label
}()
let emailLabel: UILabel = {
let label = UILabel()
label.text = "User's email"
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = GREEN_Theme
return label
}()
// Here you add a variable that is observed for SET, once a set happens bind() will be called and the views will be updated.
var currentUser : CurrentUser? {
didSet {
self.bind()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
//...
}
// setupView() should only add the UI appearance, and in this case it is calling firebase and setting currentUser, but it should not do that. This is what you have to refactor.
func setupView() {
self.addSubview(profileImageView)
self.addSubview(nameLabel)
self.addSubview(emailLabel)
self.addSubview(uidLabel)
profileImageView.anchors(top: topAnchor, topPad: 125, bottom: bottomAnchor, bottomPad: 75, left: leftAnchor, leftPad: 20, right: rightAnchor, rightPad: 250, height: 20, width: 20)
nameLabel.anchors(top: profileImageView.bottomAnchor, topPad: -50, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 0, height: 20, width: 20)
emailLabel.anchors(top: nameLabel.bottomAnchor, topPad: -80, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 125, height: 20, width: 20)
uidLabel.anchors(top: emailLabel.bottomAnchor, topPad: -40, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 0, height: 20, width: 20)
profileImageView.layer.zPosition = 10
nameLabel.layer.zPosition = 10
emailLabel.layer.zPosition = 10
profileImageView.layer.borderWidth = 2.0
profileImageView.layer.cornerRadius = 50
profileImageView.layer.borderWidth = 2.0
profileImageView.layer.borderColor = UIColor.white.cgColor
profileImageView.layer.masksToBounds = true
guard let user = Auth.auth().currentUser else { return }
let uid = user.uid
let reference = Database.database().reference().child("users").child(uid)
reference.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String : Any] else { return }
self.currentUser = CurrentUser(uid: uid, dictionary: dictionary)
}, withCancel: { (err) in
print("attempting to load information")
})
}
// This is the method that updates the cell views.
func bind() {
self.uidLabel.text = currentUser.uid
self.nameLabel.text = currentUser.name
self.emailLabel.text = currentUser.email
self.profileImageView.loadImageUsingCacheWithUrlString(currentUser.profileImageUrl)
}

my Signup controller won't display email text fields.

I have been trying to programmatically create two separate view controllers in which I have a login, and sign up view controller. While my initial view controller, Login allows me to see the email/password text field, my sign up controller does not. I have plugged in the correct information however when I run my application, it is blank. I will post my code below, can anyone help me solve this issue?
let emailTextField: UITextField = {
let e = UITextField()
let attributedPlaceholder = NSAttributedString(string: "Email", attributes:
[NSAttributedStringKey.foregroundColor : UIColor.white])
e.textColor = .white
e.attributedPlaceholder = attributedPlaceholder
e.setBottomBorder(backGroundColor: GREEN_Theme, borderColor: .white)
return e
}()
let passwordTextField: UITextField = {
let p = UITextField()
let attributedPlaceholder = NSAttributedString(string: "Password", attributes: [NSAttributedStringKey.foregroundColor : UIColor.white])
p.textColor = .white
p.isSecureTextEntry = true
p.attributedPlaceholder = attributedPlaceholder
p.setBottomBorder(backGroundColor: GREEN_Theme, borderColor: .white)
return p
}()
override func viewDidLoad (){
super.viewDidLoad()
view.backgroundColor = GREEN_Theme
func setupFileComponents() {
setupEmailField()
setupPasswordField()
}
func setupEmailField() {
view.addSubview(emailTextField)
emailTextField.anchors(top: nil, topPad: 0, bottom: nil, bottomPad: 0, left: view.leftAnchor, leftPad: 24, right: view.rightAnchor, rightPad: 24, height: 30, width: 0)
emailTextField.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
func setupPasswordField() {
view.addSubview(passwordTextField)
passwordTextField.anchors(top: emailTextField.bottomAnchor, topPad: 8, bottom: nil, bottomPad: 0, left: emailTextField.leftAnchor, leftPad: 0, right: emailTextField.rightAnchor, rightPad: 0, height: 30, width: 0)
}
Thank you in advance.
You should move setupFileComponents, setupEmailField, setupPasswordField out of viewDidLoad then you have to add te calls in viewDidLoad like that:
override func viewDidLoad (){
super.viewDidLoad()
view.backgroundColor = GREEN_Theme
setupFileComponents()
setupEmailField()
setupPasswordField()
}

UIToolbar left and right spacing when rotating device in IOS11

I'm having a strange issue with the padding of the UIToolbar since iOS11. When the device is rotated the left and right padding of the toolbar gets bigger(watch example below).
It doesn't matter if the device is in portrait or landscape mode before the rotation. The extra spacing only occurs after rotating. I think it's an auto resizing issue or something.
View Debugger before rotation (correct spacing):
https://www.dropbox.com/s/1wigv1et88t1mvn/Schermafdruk%202018-01-31%2015.51.05.png?dl=0
View Debugger after rotation (wrong spacing):
https://www.dropbox.com/s/9gnqi6hzv5czcnw/Schermafdruk%202018-01-31%2020.59.48.png?dl=0
Example:
https://www.dropbox.com/s/s7jbmbsuorump5e/spacing-toolbar.gif?dl=0
I'm using a toolbar class to create the buttons inside the toolbar.
In the xcode interface the option 'Autoresize Subview' is checked.
Code:
class ToolbarClass: UIToolbar {
//Set height of toolbar
override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
size.height = 60
return size
}
//Toolbar settings
override func layoutSubviews() {
super.layoutSubviews()
//Default
self.barStyle = UIBarStyle.default
self.sizeToFit()
//Buttons ios11+
//Space
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let spaceBetween:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
spaceBetween.width = 1.0
let nameSpace:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
nameSpace.width = 10
//Logo
let logoImage = UIImage(named: "MBS-Logo")
let logoImageView = UIImageView(image: logoImage)
logoImageView.frame = CGRect(x: -46, y: 0, width: 48, height: 54)
logoImageView.contentMode = .scaleAspectFit
let logoView = UIView(frame: CGRect(x: 0, y: 0, width: 48, height: 54))
logoView.clipsToBounds = false
logoView.layer.cornerRadius = logoView.frame.width / 2
logoView.addSubview(logoImageView)
let logoImg = UIBarButtonItem(customView: logoView)
logoImg.customView = logoView
//Profile
let profileImage = UIImage(named: "No-Profile")
let profileImageView = UIImageView(image: profileImage)
profileImageView.frame = CGRect(x: 40, y: 0, width: 50, height: 50)
profileImageView.contentMode = .scaleAspectFit
profileImageView.clipsToBounds = true
profileImageView.layer.cornerRadius = profileImageView.frame.width / 2
let profileView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
profileView.clipsToBounds = false
profileView.addSubview(profileImageView)
let profileImg = UIBarButtonItem(customView: profileView)
profileImg.customView = profileView
//NameLabel
let nameLbl = UILabel()
nameLbl.frame = CGRect(x: 0, y: 0, width: 200, height: 60)
nameLbl.text = "Hi"
nameLbl.font = UIFont(name: "Roboto", size: 22)
nameLbl.textColor = UIColor.white
let nameLabel = UIBarButtonItem()
nameLabel.customView = nameLbl
//Settings
let settingsBtn = UIButton()
settingsBtn.frame = CGRect(x: 0, y: 0, width: 64, height: 60)
settingsBtn.setImage(UIImage(named: "Settings-Bar")?.withRenderingMode(.alwaysOriginal), for: .normal)
settingsBtn.addTarget(self, action: #selector(self.settingsPressed), for: .touchUpInside)
let settingsButton = UIBarButtonItem()
settingsButton.customView = settingsBtn
//Classes
let classesBtn = UIButton()
classesBtn.frame = CGRect(x: 0, y: 0, width: 64, height: 60)
classesBtn.setImage(UIImage(named: "Classes-Bar")?.withRenderingMode(.alwaysOriginal), for: .normal)
classesBtn.addTarget(self, action: #selector(self.classesPressed), for: .touchUpInside)
let classesButton = UIBarButtonItem()
classesButton.customView = classesBtn
//Set buttons
self.setItems([profileImg, logoImg, nameSpace, nameLabel, spaceButton, classesButton, spaceBetween, settingsButton], animated: false)
}
}
First of all layoutSubviews is not the right place to add all the items. For example every time device orientate layoutSubviews is called and all items will be created again. Use init?(coder:) or init(frame:).
Once you move your code to init?(coder:) or init(frame:) you will see the left and right margin (currently it appears only when you orientate). This is actual behaviour of UIToolBar, It adds margin on both sides automatically.
To remove that margin just add negative separator of fixed size on start and end of toolbar items.
let negativeFizedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
negativeFizedSpace.width = -20 // Spacing 20 for iPad and 16 for iPhone
class ToolbarClass: UIToolbar {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
//Space
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let spaceBetween:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
spaceBetween.width = 1.0
let nameSpace:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
nameSpace.width = 10
//Logo
let logoImage = UIImage(named: "MBS-Logo")
let logoImageView = UIImageView(image: logoImage)
logoImageView.frame = CGRect(x: -46, y: 0, width: 48, height: 54)
logoImageView.contentMode = .scaleAspectFit
let logoView = UIView(frame: CGRect(x: 0, y: 0, width: 48, height: 54))
logoView.clipsToBounds = false
logoView.layer.cornerRadius = logoView.frame.width / 2
logoView.addSubview(logoImageView)
let logoImg = UIBarButtonItem(customView: logoView)
logoImg.customView = logoView
//Profile
let profileImage = UIImage(named: "No-Profile")
let profileImageView = UIImageView(image: profileImage)
profileImageView.frame = CGRect(x: 40, y: 0, width: 50, height: 50)
profileImageView.contentMode = .scaleAspectFit
profileImageView.clipsToBounds = true
profileImageView.layer.cornerRadius = profileImageView.frame.width / 2
let profileView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
profileView.clipsToBounds = false
profileView.addSubview(profileImageView)
let profileImg = UIBarButtonItem(customView: profileView)
profileImg.customView = profileView
//NameLabel
let nameLbl = UILabel()
nameLbl.frame = CGRect(x: 0, y: 0, width: 200, height: 60)
nameLbl.text = "Hi"
nameLbl.font = UIFont(name: "Roboto", size: 22)
nameLbl.textColor = UIColor.white
let nameLabel = UIBarButtonItem()
nameLabel.customView = nameLbl
//Settings
let settingsBtn = UIButton()
settingsBtn.frame = CGRect(x: 0, y: 0, width: 64, height: 60)
settingsBtn.setImage(UIImage(named: "Settings-Bar")?.withRenderingMode(.alwaysOriginal), for: .normal)
settingsBtn.addTarget(self, action: #selector(self.settingsPressed), for: .touchUpInside)
let settingsButton = UIBarButtonItem()
settingsButton.customView = settingsBtn
//Classes
let classesBtn = UIButton()
classesBtn.frame = CGRect(x: 0, y: 0, width: 64, height: 60)
classesBtn.setImage(UIImage(named: "Classes-Bar")?.withRenderingMode(.alwaysOriginal), for: .normal)
classesBtn.addTarget(self, action: #selector(self.classesPressed), for: .touchUpInside)
let classesButton = UIBarButtonItem()
classesButton.customView = classesBtn
let negativeFizedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
negativeFizedSpace.width = -20 // Spacing 20 for iPad and 16 for iPhone
//Set buttons
self.setItems([negativeFizedSpace, profileImg, logoImg, nameSpace, nameLabel, spaceButton, classesButton, spaceBetween, settingsButton, negativeFizedSpace], animated: false)
}
//Set height of toolbar
override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
size.height = 60
return size
}
}
UPDATE
If using Navigation Controller Toolbar. Alternate is to create a UIViewController extension to add generic toolbar items and call that in viewDidLoad method of your ViewController.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addGeneralToolbarItems()
}
}
class ToolbarClass: UIToolbar {
//Set height of toolbar
override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
size.height = 60
return size
}
}
extension UIViewController {
func addGeneralToolbarItems() {
//Space
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let spaceBetween:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
spaceBetween.width = 1.0
let nameSpace:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
nameSpace.width = 10
//Logo
let logoImage = UIImage(named: "MBS-Logo")
let logoImageView = UIImageView(image: logoImage)
logoImageView.frame = CGRect(x: -46, y: 0, width: 48, height: 54)
logoImageView.contentMode = .scaleAspectFit
let logoView = UIView(frame: CGRect(x: 0, y: 0, width: 48, height: 54))
logoView.clipsToBounds = false
logoView.layer.cornerRadius = logoView.frame.width / 2
logoView.addSubview(logoImageView)
let logoImg = UIBarButtonItem(customView: logoView)
logoImg.customView = logoView
//Profile
let profileImage = UIImage(named: "No-Profile")
let profileImageView = UIImageView(image: profileImage)
profileImageView.frame = CGRect(x: 40, y: 0, width: 50, height: 50)
profileImageView.contentMode = .scaleAspectFit
profileImageView.clipsToBounds = true
profileImageView.layer.cornerRadius = profileImageView.frame.width / 2
let profileView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
profileView.clipsToBounds = false
profileView.addSubview(profileImageView)
let profileImg = UIBarButtonItem(customView: profileView)
profileImg.customView = profileView
//NameLabel
let nameLbl = UILabel()
nameLbl.frame = CGRect(x: 0, y: 0, width: 200, height: 60)
nameLbl.text = "Hi"
nameLbl.font = UIFont(name: "Roboto", size: 22)
nameLbl.textColor = UIColor.white
let nameLabel = UIBarButtonItem()
nameLabel.customView = nameLbl
//Settings
let settingsBtn = UIButton()
settingsBtn.frame = CGRect(x: 0, y: 0, width: 64, height: 60)
settingsBtn.setImage(UIImage(named: "Settings-Bar")?.withRenderingMode(.alwaysOriginal), for: .normal)
settingsBtn.addTarget(self, action: #selector(self.settingsPressed), for: .touchUpInside)
let settingsButton = UIBarButtonItem()
settingsButton.customView = settingsBtn
//Classes
let classesBtn = UIButton()
classesBtn.frame = CGRect(x: 0, y: 0, width: 64, height: 60)
classesBtn.setImage(UIImage(named: "Classes-Bar")?.withRenderingMode(.alwaysOriginal), for: .normal)
classesBtn.addTarget(self, action: #selector(self.classesPressed), for: .touchUpInside)
let classesButton = UIBarButtonItem()
classesButton.customView = classesBtn
let negativeFizedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
negativeFizedSpace.width = -20 // Spacing 20 for iPad and 16 for iPhone
//Set buttons
self.setToolbarItems([negativeFizedSpace, profileImg, logoImg, nameSpace, nameLabel, spaceButton, classesButton, spaceBetween, settingsButton, negativeFizedSpace], animated: false)
}
#objc func settingsPressed() {
}
#objc func classesPressed() {
}
}

UIButton insets bug for multiline attributed title

I want to design a button respecting these rules:
Content horizontally centered
18px margin on both side of the content
10px between image and title
Title will wrap on multiple line if needed
Use attributed title
I have a working solution using:
button.setTitle(title, for: .normal)
Result:
But I can't figure why it's not working if I use:
button.setAttributedTitle(NSAttributedString(string: title), for: .normal)
Result:
It does not respect the 10px and 18px insets. Also note that the problem only occurs when the title becomes multiline.
Here is the playground I used to pinpoint my problem:
//: Playground - noun: a place where people can play
import UIKit
import PlaygroundSupport
extension UIImage {
class func imageWithView(view: UIView) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
class ActionBarView: UIView {
// MARK: Properties
private var icon: UIImage?
private let stackView = UIStackView()
private let proposeButton = UIButton()
private let declineButton = UIButton()
// MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
designView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Design
private func designView() {
designIcon()
designStackView()
designProposeButton()
designDeclineButton()
}
private func designIcon() {
let image = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
image.backgroundColor = .magenta
icon = UIImage.imageWithView(view: image)
}
private func designStackView() {
stackView.addArrangedSubview(proposeButton)
stackView.addArrangedSubview(declineButton)
stackView.distribution = .fillEqually
stackView.spacing = 1
addSubview(stackView)
stackView.frame = frame
}
private func designProposeButton() {
design(button: proposeButton,
icon: icon,
title: "Propose a task")
}
private func designDeclineButton() {
design(button: declineButton,
icon: icon,
title: "Decline")
}
private func design(button: UIButton, icon: UIImage?, title: String) {
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18)
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0)
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10)
button.titleLabel?.backgroundColor = .green
button.imageView?.backgroundColor = .orange
button.backgroundColor = .white
button.setImage(icon, for: .normal)
// button.setAttributedTitle(NSAttributedString(string: title), for: .normal)
button.setTitle(title, for: .normal)
button.titleLabel?.lineBreakMode = .byWordWrapping
}
}
let view = ActionBarView(frame: CGRect(x: 0, y: 0, width: 320, height: 44))
PlaygroundPage.current.liveView = view