UIButton Image with PDF has full width image view - ios

I'm trying to build a radio-style like button (a circle with a dot on the left, and text next to
it on the right), pretty much like this:
I have 2 PDFs (link to one of them) containing images for selected and unselected radios. My code for the button is as follows:
let radioBtn = UIButton()
radioBtn.setImage(UIImage(named: "radio", in: .module, compatibleWith: nil), for: .normal)
radioBtn.setImage(UIImage(named: "radio_ticked", in: .module, compatibleWith: nil), for: .selected)
radioBtn.contentHorizontalAlignment = .leading
radioBtn.titleLabel?.adjustsFontSizeToFitWidth = true
radioBtn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0)
radioBtn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
radioBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
radioBtn.contentVerticalAlignment = .center
radioBtn.imageView?.contentMode = .scaleAspectFit
The problem is that the UIImage stretches over the whole width (the blue part) and there is no space for thee text to show:
What I want to accomplish is the radio completely on the left, and then the text next to it with some inset. How can this be achieved?

First, your code is not setting a Width for your button. In that case, the button will set the width of its imageView to the size of the image -- with your pdf, that ends up being 510 pts wide.
So, couple options...
Use some scaling code to resize your image. If you're setting the button Height to 40, with 8-pts top and bottom insets, you need a 24x24 image.
Give your button a Width constraint, and calculate the imageView insets "on-the-fly."
Probably the easiest way to do that is with a UIButton subclass, such as this:
class RadioButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentHorizontalAlignment = .leading
// 8-pts inset "padding" on all 4 sides
contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
// set title inset Left to content inset Left
var insets: UIEdgeInsets = titleEdgeInsets
insets.left = contentEdgeInsets.left
titleEdgeInsets = insets
// set images for .normal and .selected
if let img = UIImage(named: "radio") {
setImage(img, for: .normal)
}
if let img = UIImage(named: "radio_ticked") {
setImage(img, for: .selected)
}
}
override func layoutSubviews() {
super.layoutSubviews()
// make sure an image was set (otherwise, there is no imageView)
if let imgView = imageView {
// get height of imageView
let h = imgView.frame.height
// get current (default) image edge insets
var insets: UIEdgeInsets = imageEdgeInsets
// set inset Right to width of self minus imageView Height + Left and Right content insets
insets.right = bounds.width - (h + contentEdgeInsets.left + contentEdgeInsets.right)
// update image edge insets
imageEdgeInsets = insets
}
}
}
example in use:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let radioBtn = RadioButton()
// background color so we can see its frame
radioBtn.backgroundColor = .red
radioBtn.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(radioBtn)
// give it a 22-pt bold font
radioBtn.titleLabel?.font = .boldSystemFont(ofSize: 22.0)
// set the Title
radioBtn.setTitle("Testing", for: [])
// Height: 40
radioBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
// Width: 240
radioBtn.widthAnchor.constraint(equalToConstant: 240.0).isActive = true
// centered in view
radioBtn.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
radioBtn.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
Result:

Related

Supporting dynamic font for accessibility when UILabel and UIButton are side by side in IOS [duplicate]

Large accessibility font size for UIButton title (.body or .headline) does not increase the frame of button but only increase the title text. It can clearly be seen in the screenshot below:
Constraints are only top, leading and trailing, also in code I have added 2 lines:
button.adjustsImageSizeForAccessibilityContentSizeCategory = true
button.titleLabel?.numberOfLines = 0
The yellow background colour is of the button, thus identifying that only tappable area is the yellow area. I want to increase the button frame so that the whole text area becomes tappable.
There is an issue with multi-line button labels to begin with - I don't think it's directly related to using accessibility fonts.
Try using this button subclass:
class MultilineTitleButton: UIButton {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() -> Void {
self.titleLabel?.numberOfLines = 0
self.titleLabel?.textAlignment = .center
self.setContentHuggingPriority(UILayoutPriority.defaultLow + 1, for: .vertical)
self.setContentHuggingPriority(UILayoutPriority.defaultLow + 1, for: .horizontal)
}
override var intrinsicContentSize: CGSize {
let size = self.titleLabel!.intrinsicContentSize
return CGSize(width: size.width + contentEdgeInsets.left + contentEdgeInsets.right, height: size.height + contentEdgeInsets.top + contentEdgeInsets.bottom)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
}
}
It sets the titleLable's .numberOfLines = 0, .textAlignment = .center, and hugging priorities, and then override's intrinsicContentSize to tell auto-layout the proper size of the titleLabel.
Possible solution is :
button.titleLabel?.numberOfLines = 0
button.titleLabel?.lineBreakMode = .byWordWrapping
button.titleLabel?.masksToBounds = true
button.sizeToFit()

Text overlapping issue in Textfield in iOS Swift

In my textfield there is a right view and datepicker added. When I select the date, text is overlapping with the right view.
I am using a Designable class for adding right view. How can I fix overlapping issue
Here is the code for setting up rightview
rightViewMode = UITextField.ViewMode.always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 17, height: 17))
imageView.contentMode = .scaleAspectFit
imageView.image = rightImage
imageView.tintColor = color
// Added containerView for repositioning image
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 20))
self.addSubview(containerView)
containerView.addSubview(imageView)
rightView = containerView
Without seeing all your code it's kind of hard to tell what is going on here. Do you use storyboards and constraints? Or do you hardcode all the frames of your views?
What I would do here, is using a UIStackView and constraints, because it helps getting rid of all the hardcoded positioning values, and it gives you much more flexibility for laying out your UI.
let textField = UITextField()
textField.placeholder = "16 December 2018"
let imageView = UIImageView()
imageView.backgroundColor = .systemBlue
let stackView = UIStackView(arrangedSubviews: [textField, imageView])
stackView.axis = .horizontal
stackView.distribution = .fill
stackView.alignment = .center
stackView.spacing = 10
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 17).isActive = true
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
As you can see I set the stack view's distribution property to .fill here, so because the width of your image is constrained to 17, your text field width will adjust to fill the width of the stack view. You may want to adjust this property, and the spacing property, depending on what kind of behaviour you're looking for.
I have solved my problem with this peace of code:
Added Padding For Textfields
let padding = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}

Is it possible to use Auto Layout in a UITextField's leftView?

I want to customize a UITextField's leftView with a view that is automatically sized depending on its contents:
func set(leftImage image: UIImage) {
let imageView = UIImageView(image: image)
let paddingContainer = UIView()
// This is the crucial point:
paddingContainer.translatesAutoresizingMaskIntoConstraints = false
paddingContainer.addSubview(imageView)
imageView.pin(toMarginsOf: paddingContainer)
leftView = paddingContainer
leftViewMode = .always
}
where the pin method just pins the image view on all four sides to the margins of the paddingContainer:
func pin(toMarginsOf view: UIView) {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor)
])
}
On iOS 12, everything works as expected, but on iOS versions < 12, the image is completely misplaced. It's not even within the bounds of the text field but in the upper left corner of my view controller's view.
To me it seems like older versions of iOS don't support using Auto Layout inside the view that you set as a text field's leftView. The documentation states:
The left overlay view is placed in the rectangle returned by the leftViewRect(forBounds:) method of the receiver.
but it doesn't state how it's placed there: By using constraints or by setting the frame directly.
Are there any reliable sources or educated guesses if using Auto Layout is supported at all for the leftView?
extension UITextField{
func setLeft(image: UIImage, withPadding padding: CGFloat = 0) {
let wrapperView = UIView.init(
frame: CGRect.init(
x: 0,
y: 0,
width: bounds.height,
height: bounds.height
)
)
let imageView = UIImageView()
imageView.image = image
imageView.contentMode = .scaleAspectFit
wrapperView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(
equalTo: wrapperView.leadingAnchor,
constant: padding
),
imageView.trailingAnchor.constraint(
equalTo: wrapperView.trailingAnchor,
constant: -padding
),
imageView.topAnchor.constraint(
equalTo: wrapperView.topAnchor,
constant: padding
),
imageView.bottomAnchor.constraint(
equalTo: wrapperView.bottomAnchor,
constant: -padding
)
])
leftView = wrapperView
leftViewMode = .always
}
}
hope this will help
You can set its frame on layout subviews function like this
override func layoutSubviews() {
super.layoutSubviews()
if let lv = self.leftView {
lv.frame = CGRect(x: 0, y: 0, width: self.bounds.height, height: self.bounds.height)
}
}

Swift iOS - How to line up a sublayer position to match the center of button text

I have a button that is text. I added a red background subLayer and I made the backgroundLayer's width and height bigger then the button text. I tried to center the background layer to the button using:
backgroundLayer.position = button.center
It's not centering. This is what I get:
I know I can set the background color and cornerRadius on the button directly but when I do it that way the red background hugs the text:
button.backgroundColor = UIColor.red
button.layer.cornerRadius = 10
I want the redbackground to be wider and taller then the text:
backgroundLayer.frame = CGRect(x: 0, y: 0, width: buttonTextSize.width + 10, height: buttonTextSize.height + 5
I would Photoshop an example but I don't have Photshop in front of me at the moment. This is the closest I can find. This is a button from Vimeo. They aren't using text but the backgroundLayer is much wider and taller then the button image and the backgroundLayer's position is aligned with the button's midX and midY:
How do I get the position of the background subLayer to line up with the center of the button's text?
let button: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Next", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.contentHorizontalAlignment = .center
button.titleLabel?.font = UIFont.systemFont(ofSize: 23)
return button
}()
let backgroundLayer: CALayer = {
let layer = CALayer()
layer.backgroundColor = UIColor.red.cgColor
layer.cornerRadius = 10
return layer
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let buttonText = button.titleLabel?.text
let buttonTextSize = (buttonText! as NSString).size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 23.0)])
// I added 10 points to the backgroundLayer's width and 5 points to the backgroundLayer's height so its wider then the text
backgroundLayer.frame = CGRect(x: 0, y: 0, width: buttonTextSize.width + 10, height: buttonTextSize.height + 5)
button.layer.insertSublayer(backgroundLayer, at: 0)
backgroundLayer.position = button.center
}
Here's a button that seems to look the way you want (of course you can adjust any parameters that don't suit your sensibilities):
This button is automatically red, corner-rounded, and considerably larger than its text (even when the button is positioned using auto layout).
Here's how it was achieved through a subclass:
class MyRedButton : UIButton {
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundColor = .red
self.layer.cornerRadius = 10
}
override var intrinsicContentSize: CGSize {
var sz = super.intrinsicContentSize
sz.width += 30; sz.height += 30
return sz
}
}
Matt's upvoted answer is correct.
Two things he pointed out to me in the comments that I was initially doing wrong was.
I tried to set backgroundLayer.position = button.center. This is wrong because the button's center is based on the frame's center and not it's bounds center. I should've set the backgroundLayer.position to match the center of the button's bounds
I tried to set the backgroundLayer's position to the button's center in viewWillLayoutSubviews which he said the button's bounds weren't known yet so the backgroundLayer had no information to base it on. I was supposed to add the code to viewDidLayoutSubviews
Here's the code here:
// 1. add the code to viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let text = button.titleLabel?.text
let textSize = (text! as NSString).size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 23.0)])
backgroundLayer.frame = CGRect(x: 0, y: 0, width: textSize.width + 10, height: textSize.height + 5)
button.layer.insertSublayer(backgroundLayer, at: 0)
// 2. get the buttons bound's center by accessing it's midX and midY
let buttonMidX = button.bounds.midX
let buttonMidY = button.bounds.midY
let buttonBoundsCenter = CGPoint(x: buttonMidX, y: buttonMidY)
// 3. set the backgroundLayer's postion to the buttonBoundsCenter
backgroundLayer.position = buttonBoundsCenter
}
And it works:

Why textView do not accommodate text kept inside it?

I have a plain textView and text inside it. With boundingRect i determine size based on text property of UITextView. Then i apply it to the size of my cell of collectionView. But text do not lay completely inside textView. but if i add to the width of the retrieved size 10 points everything works ok
Maybe i did not take something into account when was applying size to the cell?
Here i created my textView inside cell Class:
class experimentalCell: BaseCellInAppStoreFolder {
lazy var familySharingView: UITextView = {
let tv = UITextView()
tv.text = "Family Sharing"
tv.textAlignment = .right
tv.backgroundColor = UIColor.green
tv.textContainer.maximumNumberOfLines = 1
tv.font = UIFont.systemFont(ofSize: 11)
let options = NSStringDrawingOptions.usesLineFragmentOrigin
let dummySize = CGSize(width: 1000, height: self.frame.height - 16)
let rect = tv.text?.boundingRect(with: dummySize, options: options, context: nil)
tv.textContainerInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
return tv
}()
override func setupViews() {
super.setupViews()
addSubview(textView)
addConstraintsWithFormat("H:|[v0]|", views: textView)
addConstraintsWithFormat("V:|[v0]|", views: textView)
}
}
Then I applied size obtained form rect property to size of my cell.

Resources