I have a UIButton on the screen. There are no width constraints on the UIButton. I like my UIButton to be expanded to the assigned text. But here is the result:
Here is the implementation:
self.translatedPhraseButton.setTitle(self.selectedPhrase.translatedPhrase, for: .normal)
self.translatedPhraseButton.sizeToFit()
self.translatedPhraseButton.titleEdgeInsets = UIEdgeInsets(top: 0.0, left: 5.0, bottom: 0.0, right: 5.0)
self.translatedPhraseButton.layer.cornerRadius = 10.0
self.translatedPhraseButton.layer.masksToBounds = true
self.translatedPhraseButton.backgroundColor = UIColor(fromHexString: "2aace3")
So, I finally resolved my issue by using a single line of code:
self.translatedPhraseButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 15.0, bottom: 0.0, right: 15.0)
Try creating a temporary label, then setting the button's size to that label's.
let label = UILabel()
label.text = button.titleLabel?.text
label.font = button.titleLabel?.font
label.sizeToFit()
yourButton.frame.size = label.frame.size
Also, you can adjust the button's titleLabel to shrink the text to have it fit:
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.minimumScaleFactor = 0.5
Problem:
The reason why the text gets truncated is because of the following line:
self.translatedPhraseButton.titleEdgeInsets = UIEdgeInsets(top: 0.0, left: 5.0, bottom: 0.0, right: 5.0)
You have added 10.0 padding to the title label, which causes the text to truncate.
Solution:
I have used Swift 3 (It wouldn't be hard to change it to Swift 2 if you need)
Button:
class RoundedCornerButton : UIButton {
override func draw(_ rect: CGRect) {
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: [.topLeft, .topRight, .bottomLeft, .bottomRight],
cornerRadii: CGSize(width: 10, height: 10))
UIColor.red.setFill()
path.fill()
}
override var intrinsicContentSize: CGSize {
let originalSize = super.intrinsicContentSize
let size = CGSize(width: originalSize.width + 10, height: originalSize.height)
return size
}
}
Invoking:
let translatedPhraseButton = RoundedCornerButton()
translatedPhraseButton.setTitle("haskjhdjk", for: .normal)
view.addSubview(translatedPhraseButton)
translatedPhraseButton.translatesAutoresizingMaskIntoConstraints = false
translatedPhraseButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
translatedPhraseButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
Related
I want to create something like this:
Output
This is what I've tried so far:
class RoundedShadowCorners {
func shadowTopBar(_ topBar: UINavigationBar,_ offset: CGFloat,_ navigationItem: UINavigationItem){
topBar.isTranslucent = false
topBar.tintColor = UIColor.orange
topBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
topBar.shadowImage = UIImage()
topBar.backgroundColor = UIColor.white
let shadowView = UIView(frame: CGRect(x: 0, y: -offset, width: (topBar.bounds.width), height: (topBar.bounds.height) + offset))
shadowView.backgroundColor = UIColor.white
topBar.insertSubview(shadowView, at: 1)
let shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: shadowView.bounds, byRoundingCorners: [.bottomLeft , .bottomRight , .topLeft], cornerRadii: CGSize(width: 20, height: 20)).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 2
shadowView.layer.insertSublayer(shadowLayer, at: 0)
topBar.prefersLargeTitles = true
topBar.topItem?.title = "HJFSKDJKA"
}
}
Problem with this is that this makes the title text be behind the actual NavigationBar and I can only make it come forward if I create a new UIView for the title and try positioning it on the screen, which makes it extremely difficult to be responsive.
offset isview.safeAreaInsets.top
I would prefer to do it without making a new UIView, because it makes things complicated, but I couldn't even begin to do it.
This is updated & tested code, there were certain lines which needs to be updated to overcome this behaviour,go through my in-line comments for details.
func shadowTopBar(_ topBar: UINavigationBar,_ offset: CGFloat){
// Set the prefers title style first
// since this is how navigation bar bounds gets calculated
//
topBar.prefersLargeTitles = true
topBar.topItem?.title = "HJFSKDJKA"
topBar.isTranslucent = false
topBar.tintColor = UIColor.orange
topBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
topBar.shadowImage = UIImage()
topBar.backgroundColor = UIColor.white
// Make your y position to the max of the uiNavigationBar
// Height should the cornerRadius height, in your case lets say 20
let shadowView = UIView(frame: CGRect(x: 0, y: topBar.bounds.maxY, width: (topBar.bounds.width), height: 20))
// Make the backgroundColor of your wish, though I have made it .clear here
// Since we're dealing it in the shadow layer
shadowView.backgroundColor = .clear
topBar.insertSubview(shadowView, at: 1)
let shadowLayer = CAShapeLayer()
// While creating UIBezierPath, bottomLeft & right will do the work for you in this case
// I've removed the extra element from here.
shadowLayer.path = UIBezierPath(roundedRect: shadowView.bounds, byRoundingCorners: [.bottomLeft , .bottomRight], cornerRadii: CGSize(width: 20, height: 20)).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
// This too you can set as per your desired result
shadowLayer.shadowOffset = CGSize(width: 2.0, height: 4.0)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 2
shadowView.layer.insertSublayer(shadowLayer, at: 0)
}
Here's a detailed article on this. Medium
I have a custom Button with 2 Labels and Image as shown in below class
import UIKit
#IBDesignable
class CustomButton : UIButton {
let secondLine : UILabel = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
// self.layer.cornerRadius = 10
self.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
self.layer.shadowOpacity = 1.0
self.layer.masksToBounds = false
}
#IBInspectable var rightLebelText : String?{
didSet {
updateView()
}
}
func updateView(){
if let mytext = rightLebelText {
let firstLine = UILabel(frame: CGRect(x: self.bounds.size.width - 210, y: 0, width: 200, height: 40))
firstLine.text = mytext
firstLine.textAlignment = .right
firstLine.font = UIFont.systemFont(ofSize: 20)
self.addSubview(firstLine)
var imageView : UIImageView
imageView = UIImageView(frame:CGRect(x: 5, y: 10, width: 20, height: 20))
imageView.image = UIImage(named:"arrow.png")
self.addSubview(imageView)
}
}
public func setSecondlabel(title : String){
secondLine.removeFromSuperview()
secondLine.frame = CGRect(x: 50, y: 0, width: 200, height: 40)
secondLine.text = title
secondLine.font = UIFont.boldSystemFont(ofSize: 20)
secondLine.removeFromSuperview()
self.addSubview(secondLine)
}
}
My issue is my view size is not updating on different devices when using
self.bounds.size.width
for the firstLine label as shown in below image its position should be on the custom button right edge
You need to override the layoutSubviews function have the frame of each element examine and update based on updated bounds of the custom view or assign proper layout constraints on each element while adding it.
If you are overriding the UIButton which has already a label and image property, you can use that one as well or create a custom class inherited from UIControl and create required three property as needed. I am adding an example of the custom class with image, title, and detail as shown in the problem.
class CustomButton : UIControl {
let imageView : UIImageView
let titleLabel : UILabel
let detailLabel : UILabel
fileprivate func setup() {
self.detailLabel.textAlignment = .right
self.detailLabel.font = UIFont.systemFont(ofSize: 20)
self.detailLabel.textColor = UIColor.black
self.titleLabel.font = UIFont.boldSystemFont(ofSize: 20)
self.titleLabel.textColor = UIColor.black
self.backgroundColor = UIColor.white
self.addSubview(self.imageView)
self.addSubview(self.titleLabel)
self.addSubview(self.detailLabel)
}
override init(frame: CGRect) {
self.imageView = UIImageView(frame: .zero)
self.titleLabel = UILabel(frame: .zero)
self.detailLabel = UILabel(frame: .zero)
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
self.imageView = UIImageView(frame: .zero)
self.titleLabel = UILabel(frame: .zero)
self.detailLabel = UILabel(frame: .zero)
super.init(coder: aDecoder)
self.setup()
}
override func layoutSubviews() {
super.layoutSubviews()
self.imageView.frame = CGRect(x: 5.0, y: self.bounds.midY - 10.0, width: 20.0, height: 20.0)
//You can make this width dynamic if you want to calculate width of text using self.detailLabel.text
var width : CGFloat = 200.0
self.titleLabel.frame = CGRect(x: self.imageView.frame.maxX + 5.0, y: self.bounds.minY, width: 200.0, height: self.bounds.height)
//Give the remaining space to the second label
width = self.bounds.width - (self.titleLabel.frame.maxX + 15.0)
self.detailLabel.frame = CGRect(x: self.titleLabel.frame.maxX + 5.0, y: self.bounds.minY, width: width, height: self.bounds.height)
}
}
Inside willWillLayoutSubviews() I call UIButton's method setTileTheme(), which I created. Result can be seen below - duplicate UILabel appears under another one. I have already tried calling my method from viewDidLoad() etc., but it didn't help.
Do someone know why I am facing this problem?
func setTileTheme(image: UIImage, title: String) {
self.translatesAutoresizingMaskIntoConstraints = false
tintColor = .green
backgroundColor = .white
setBorder(width: 1.5, color: .lightGray)
roundCorners(radius: 5)
self.layer.masksToBounds = true
let width = self.frame.size.width
let height = self.frame.size.height
let offset: CGFloat = width/4.5
let titleLabel = UILabel(frame: CGRect(x: 0.0, y: 0.0, width: width, height: 30))
titleLabel.center = CGPoint(x: width/2, y: height-offset)
titleLabel.text = title
titleLabel.font = titleLabel.font.withSize(15)
titleLabel.textAlignment = .center
titleLabel.textColor = .darkGray
self.insertSubview(titleLabel, at: 0)
imageEdgeInsets = UIEdgeInsets(top: height/8, left: width/4, bottom: height*3/8, right: width/4)
setImage(image, for: .disabled)
setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
}
UIButton already has titleLabel and imageView. What you are doing is you are creating a new label and adding it to buttons view which will not replace the default label.
All that you need is
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRectCGRect(x: 0.0, y: 0.0, width: self.frame.size.width, height: 30)
}
func setTileTheme(image: UIImage, title: String) {
self.translatesAutoresizingMaskIntoConstraints = false
tintColor = .green
backgroundColor = .white
setBorder(width: 1.5, color: .lightGray)
roundCorners(radius: 5)
self.layer.masksToBounds = true
let width = self.frame.size.width
let height = self.frame.size.height
let offset: CGFloat = width/4.5
self.titleLabel?.text = title
self.titleLabel?.font = titleLabel.font.withSize(15)
self.titleLabel?.textAlignment = .center
self.titleLabel?.textColor = .darkGray
imageEdgeInsets = UIEdgeInsets(top: height/8, left: width/4, bottom: height*3/8, right: width/4)
setImage(image, for: .disabled)
setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
}
I believe you were trying to set the button's title label frame which you can easily set to the default title label of button itself by overriding titleRect
EDIT:
I can see that you are trying to set the inset to Button's image as well. Adding inset to imageView will simply move the image inside imageView but the imageView frame remains the same. Rather in case you wanna affect the imageView's frame itself then you can simply override
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
//whatever calculation you wanna provide
//for example
return CGRect(x: (self.bounds.size.width/2 + 5), y: self.bounds.size.height/2, width: (self.bounds.size.width/2 - 5), height: self.bounds.size.height)
}
Hope it helps
I have a UICollectionViewCell which has a UIStackView that fills the whole cell.
Inside that UIStackView I am trying to add subviews but I'm running into two problems here.
First one is that I cannot see the UIStackView being added to the cell, even setting the stack view background color to a different one it doesn't show.
The second problem is that my app - in this case it is a Today Extension Widget crashes when I'm instantiating a button image as button.setImage(:_)
Here's the code:
#objc lazy var composeButton: UIButton = {
let button = UIButton(type: .system)
let image = #imageLiteral(resourceName: "compose_icon").withRenderingMode(.alwaysOriginal)
button.setImage(image, for: .normal)
button.addTarget(self, action: #selector(didTapCompose), for: .touchUpInside)
return button
}()
let stackview = UIStackView(arrangedSubviews: [composeButton, reloadButton])
stackview.distribution = .fillEqually
stackview.axis = .horizontal
addSubview(stackview)
stackview.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddinfLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
extension UIView {
/// Anchor a specific view to a superview
#objc func anchor(top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddinfLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: paddinfLeft).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if width != 0 {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if height != 0 {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
/// Rounds the corners of a UIView object
/// parameters: corners - the corners to round (upperLeft, upperRight, lowerLeft, lowerRight);
/// radiud - the desired cornerRadius to apply to the desired corners
#objc func roundCorners(corners:UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
}
The app crashes on this line
button.setImage(image, for: .normal)
It crashes with a SIGABRT error as shown in the image bellow.
Does anyone spot the problem here ?
Since this is part of a Today Extension Widget, make sure you've included the image in that build target.
A future debugging tip: When you see an error that "really shouldn't happen," try thinking about step-by-step. In this case, while the .setImage line appeared to be the culprit, if you had checked the validity of your let image = line, you might have saved a little time.
I have a UIButton with image and text. How do I align the image to the right, and align the text to the left?
Use below two methods
button.imageEdgeInsets = UIEdgeInsets(top: 0.0, left: 0, bottom: 0.0, right: 0.0)
button.titleEdgeInsets = UIEdgeInsets(top: 0.0, left: 0, bottom: 0.0, right: 0.0)
Pass value for left and right and adjust it.
Set offset as per you requirement.
#IBDesignable class RightImageButton: UIButton {
#IBInspectable var alignImageToRight: Bool = true
override func layoutSubviews() {
super.layoutSubviews()
if alignImageToRight {
if let imageView = self.imageView, let titleLabel = self.titleLabel {
let imageWidth = imageView.frame.size.width
var imageFrame: CGRect = imageView.frame;
var labelFrame: CGRect = titleLabel.frame;
labelFrame.origin.x = self.titleEdgeInsets.left + self.contentEdgeInsets.left
imageFrame.origin.x = frame.width - self.imageEdgeInsets.right - self.contentEdgeInsets.right - imageWidth
imageView.frame = imageFrame;
titleLabel.frame = labelFrame;
}
}
}
}