Increasing hit area for UITapGestureRecognizer on UILabel - swift

I'm trying to increase the hit area of a UITapGestureRecognizer on a UILabel object. This answer suggests overriding hitTest on the UILabel:
class PaddedLabel: UILabel {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
print("hitTest called")
let padding: CGFloat = 20.0
let extendedBounds = bounds.insetBy(dx: -padding, dy: -padding)
return extendedBounds.contains(point) ? self : nil
}
}
However, the problem is that hitTest is not even called unless I'm actually tapping on the object, and not somewhere close to it. Therefore, extending the bounds seems to be useless.
The label is one of a few inside a UIStackView:
let label = PaddedLabel()
let gs = UITapGestureRecognizer(target: self, action: #selector(ThisViewController.handleTap(_:)))
label.addGestureRecognizer(gs)
label.isUserInteractionEnabled = true
stackView.addArrangedSubview(label)
How do I make this work?

You can edit PaddedLabel like below to set insets:
class PaddedLabel: UILabel {
var textInsets = UIEdgeInsets.zero {
didSet { invalidateIntrinsicContentSize() }
}
override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
let invertedInsets = UIEdgeInsets(top: -textInsets.top,
left: -textInsets.left,
bottom: -textInsets.bottom,
right: -textInsets.right)
return textRect.inset(by: invertedInsets)
}
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: textInsets))
}
}
and set textInsets to enlarge its tappable area.
label.textInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

Related

Custom Floating UITextField animation

everyone. I'm trying to make a floating textfield. I couldn't somehow move the placeholderLabel up the animation when the TextField is clicked.
I just tried to change the testPlaceholder.centerYAnchor but without success..
Can I develop TextField as Nib(xib)?
If I enhance TextField as Nib(xib) I can define IBOutlet as NSLayoutConstraint and animate with UIView I can change anchor in an animated way.
Finally, I want to reduce the font's size in an animated way.
CustomTextField:
class TestTextField: UITextField {
lazy var testPlaceholder: UILabel = {
let label = UILabel()
label.text = "placeholder"
return label
}()
lazy var centerConstraint: NSLayoutConstraint = {
let constraint = NSLayoutConstraint()
return constraint
}()
let padding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
override open func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.addSubview(testPlaceholder)
addConstraint()
self.delegate = self
}
func addConstraint() {
testPlaceholder.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
testPlaceholder.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 6),
testPlaceholder.centerYAnchor.constraint(equalTo: self.centerYAnchor)
])
}
func placeHolderUp() {
UIView.animate(withDuration: 1) {
self.testPlaceholder.transform = CGAffineTransform(translationX: 0, y: -18)
}
self.setNeedsLayout()
self.layoutIfNeeded()
}
func activeTextFieldColor() {
self.layer.borderColor = UIColor.green.cgColor
placeHolderUp()
}
func deactiveTextFieldColor() {
self.layer.borderColor = UIColor.red.cgColor
}
}
extension TestTextField: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
self.layer.borderColor = UIColor.green.cgColor
self.textColor = .green
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.layer.borderColor = UIColor.red.cgColor
self.textColor = .red
}
}

UItextfield text covers the clear button in the textfield in Swift

Hello, I'm working on a UITextField now and as the image above shows, the text covers the clear button and I want to fix that.
In this text field, I added a padding on the left side of the text and the right side of the clear button.
class testTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func commonInit() {
let leftPadding = UIView(frame: CGRect(x: 0, y: 0, width: 17, height: 0))
leftPadding.backgroundColor = .clear
self.leftView = leftPadding
self.leftViewMode = .always
// tried adding padding to the rightView, but it hide the clear button
// let rightPadding = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 0))
// rightPadding.backgroundColor = .red
// self.rightView = rightPadding
// self.rightViewMode = .always
self.backgroundColor = .white
self.borderStyle = .none
self.layer.cornerRadius = 12
self.font = UIFont.systemFont(ofSize: 16)
self.textColor = .black
self.clearButtonMode = .whileEditing
self.isUserInteractionEnabled = true
}
override func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
let originalRect = super.clearButtonRect(forBounds: bounds)
return originalRect.offsetBy(dx: -22, dy: 0)
}
// also tried changing the width of the textRext, but after I implement the following code, the textfield becomes non-clickable...
// override func textRect(forBounds bounds: CGRect) -> CGRect {
// self.bounds.size.width = bounds.size.width - 20.0;
// return bounds;
// }
// override func editingRect(forBounds bounds: CGRect) -> CGRect {
// self.bounds.size.width = bounds.size.width - 20.0;
// return bounds;
// }
}
After doing some research(like this), as I put some comments in the code, I tried adding padding to rightView and changing the textRect, but none of them worked.
I want to keep the clear button where it is now, but I want some space on the left side of the clear button, something like the image below...
Thanks to #Shadowrun, I was able to solve the issue I had.
I needed to subclass the UITextField and add editingRect. Then return the CGRect with whatever numbers I want to put. My code is something like this.
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(x: 17, y: 0, width: bounds.size.width - 70, height: bounds.size.height)
}

Slider with custom thumb image and text

Hy,
I'm trying to customize a slider by changing the thumb image and add a label over the picture.
For this, in my view in I set the image for slider thumb:
class SliderView: NibLoadingView {
#IBOutlet weak var slider: ThumbTextSlider!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
contentView = legacyLoadXib()
setup()
}
override func setup() {
super.setup()
self.slider.setThumbImage(UIImage(named: "onb_cc_slider_thumb")!, for: .normal)
self.slider.thumbTextLabel.font = UIFont(name: Fonts.SanFranciscoDisplayRegular, size: 14)
}
}
In ThumbTextSlider class I set the text label as below:
class ThumbTextSlider: UISlider {
var thumbTextLabel: UILabel = UILabel()
private var thumbFrame: CGRect {
return thumbRect(forBounds: bounds, trackRect: trackRect(forBounds: bounds), value: value)
}
override func layoutSubviews() {
super.layoutSubviews()
thumbTextLabel.frame = thumbFrame
}
override func awakeFromNib() {
super.awakeFromNib()
addSubview(thumbTextLabel)
thumbTextLabel.layer.zPosition = layer.zPosition + 1
}
}
When I made test the result was a little different.
How, can I fix the issue ?
The expected result:
Kind regards
This class may help you. In class instead of image I created image you can replace with you thumb image.
class ThumbTextSlider: UISlider {
private var thumbTextLabel: UILabel = UILabel()
private var thumbFrame: CGRect {
return thumbRect(forBounds: bounds, trackRect: trackRect(forBounds: bounds), value: value)
}
private lazy var thumbView: UIView = {
let thumb = UIView()
return thumb
}()
override func layoutSubviews() {
super.layoutSubviews()
thumbTextLabel.frame = CGRect(x: thumbFrame.origin.x, y: thumbFrame.origin.y, width: thumbFrame.size.width, height: thumbFrame.size.height)
self.setValue()
}
private func setValue() {
thumbTextLabel.text = self.value.description
}
override func awakeFromNib() {
super.awakeFromNib()
addSubview(thumbTextLabel)
thumbTextLabel.textAlignment = .center
thumbTextLabel.textColor = .blue
thumbTextLabel.adjustsFontSizeToFitWidth = true
thumbTextLabel.layer.zPosition = layer.zPosition + 1
let thumb = thumbImage()
setThumbImage(thumb, for: .normal)
}
private func thumbImage() -> UIImage {
let width = 100
thumbView.frame = CGRect(x: 0, y: 15, width: width, height: 30)
thumbView.layer.cornerRadius = 15
let renderer = UIGraphicsImageRenderer(bounds: thumbView.bounds)
return renderer.image { rendererContext in
rendererContext.cgContext.setShadow(offset: .zero, blur: 5, color: UIColor.black.cgColor)
thumbView.backgroundColor = .red
thumbView.layer.render(in: rendererContext.cgContext)
}
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(origin: bounds.origin, size: CGSize(width: bounds.width, height: 5))
}
}

Is possible make a UITextField like this?

Situation: I want to use a custom UITextField class for my textField in a xcode project.
I want to the textField look like this:
I had no problems in making the edges rounded, and change the color of my placeholder, but I have no idea how to keep the bottom edges flat and draw a black border only on the bottom.
This is my code:
import UIKit
class GrayTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
backgroundColor = .grayf1f1f1
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
layer.cornerRadius = 10
clipsToBounds = true
}
override var placeholder: String? {
didSet {
let attributes = [ NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16, weight: .thin)]
attributedPlaceholder = NSAttributedString(string: placeholder ?? "", attributes: attributes)
}
}
}
And my current result:
In your answer, still there is some issue in bottom left and right corner.
To achieve exact result, change your UITextField Border Style to No Border.
Padding for Text:
class GrayTextField: UITextField {
let padding = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 5)
..... Your Exact Code .....
override open func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
}
OutPut
Well, after some researching, i do it.
Here if my final code:
import UIKit
class GrayTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
backgroundColor = .grayf1f1f1
clipsToBounds = true
let maskPath = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: 10.0, height: 10.0))
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
layer.mask = shape
addBottomBorder(with: .darkGray, andWidth: 1)
}
override var placeholder: String? {
didSet {
let attributes = [ NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16, weight: .thin)]
attributedPlaceholder = NSAttributedString(string: placeholder ?? "", attributes: attributes)
}
}
func addBottomBorder(with color: UIColor?, andWidth borderWidth: CGFloat) {
let border = UIView()
border.backgroundColor = color
border.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
border.frame = CGRect(x: 0, y: frame.size.height - borderWidth, width: frame.size.width, height: borderWidth)
addSubview(border)
}
}
And this is the result:

What is the best way to auto-resize a UIButton based on its contents, mainly its title and image?

I have written the below code in order to generate a custom UIButton, which I intend to use in different locations across my applications:
import UIKit
import ChameleonFramework
class CustomButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
convenience init(buttonTitleTexForNormalState titleForNormalState: String, buttonTitleTextColourForNormalState normalTextColour: UIColor, buttonTitleTextColourForHighlightedState highlightedTextColour: UIColor, buttonTitleFontType fontType: String, buttonTitleFontSize fontSize: CGFloat, buttonBackgroundHexColourCode hexColour: String, buttonFrameCornerRadius cornerRadius: CGFloat, buttonFrameBorderWidth borderWidth: CGFloat, buttonFrameBorderColour borderColour: String, buttonBackgroundTransperancyAlphaValue transperancy: CGFloat, buttonTagNumber tagValue: Int, buttonTarget: Any?, buttonSelector: Selector, buttonImageForNormalState normalButtonImage: String) {
self.init()
setupButtonEssentials(buttonTitleTexForNormalState: titleForNormalState, buttonTitleTextColourForNormalState: normalTextColour, buttonTitleTextColourForHighlightedState: highlightedTextColour, buttonTitleFontType: fontType, buttonTitleFontSize: fontSize, buttonBackgroundHexColourCode: hexColour, buttonFrameCornerRadius: cornerRadius, buttonFrameBorderWidth: borderWidth, buttonFrameBorderColour: borderColour, buttonBackgroundTransperancyAlphaValue: transperancy, buttonTagNumber: tagValue, buttonTarget: buttonTarget, buttonSelector: buttonSelector, buttonImageForNormalState: normalButtonImage)
}
func setupButtonEssentials(buttonTitleTexForNormalState titleForNormalState: String, buttonTitleTextColourForNormalState normalTextColour: UIColor, buttonTitleTextColourForHighlightedState highlightedTextColour: UIColor, buttonTitleFontType fontType: String, buttonTitleFontSize fontSize: CGFloat, buttonBackgroundHexColourCode hexColour: String, buttonFrameCornerRadius cornerRadius: CGFloat, buttonFrameBorderWidth borderWidth: CGFloat, buttonFrameBorderColour borderColour: String, buttonBackgroundTransperancyAlphaValue transperancy: CGFloat, buttonTagNumber tagValue: Int, buttonTarget: Any?, buttonSelector: Selector, buttonImageForNormalState normalButtonImage: String) {
setTitleColor(normalTextColour, for: .normal)
setTitleColor(highlightedTextColour, for: .highlighted)
titleLabel?.font = UIFont(name: fontType, size: fontSize)
setTitle(titleForNormalState, for: .normal)
backgroundColor = UIColor(hexString: hexColour)
layer.cornerRadius = cornerRadius
layer.borderWidth = borderWidth
layer.borderColor = UIColor(hexString: borderColour)?.cgColor
showsTouchWhenHighlighted = true
alpha = transperancy
contentHorizontalAlignment = .center
self.tag = tagValue
addTarget(target, action: buttonSelector, for: .touchUpInside)
if let buttonImage = UIImage(named: normalButtonImage) {
setImage(buttonImage.withRenderingMode(.alwaysOriginal), for: .normal)
contentMode = .scaleAspectFit
}
setShadow()
translatesAutoresizingMaskIntoConstraints = false
}
private func setShadow() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0.0, height: 6.0)
layer.shadowRadius = 8
layer.shadowOpacity = 0.5
clipsToBounds = true
layer.masksToBounds = false
}
func shake() {
let shake = CABasicAnimation(keyPath: "position")
shake.duration = 0.1
shake.repeatCount = 2
shake.autoreverses = true
let fromPoint = CGPoint(x: center.x-8, y: center.y)
let fromValue = NSValue(cgPoint: fromPoint)
let toPoint = CGPoint(x: center.x+8, y: center.y)
let toValue = NSValue(cgPoint: toPoint)
shake.fromValue = fromValue
shake.toValue = toValue
layer.add(shake, forKey: "position")
}
}
And the below code illustrates an example of where I created an instance from the custom UIButton class (as demonstrated above) inside a ViewController:
import UIKit
import ChameleonFramework
class MainScreen: UIViewController, UINavigationBarDelegate {
var newFileButton = CustomButton(buttonTitleTexForNormalState: "New...", buttonTitleTextColourForNormalState: .black, buttonTitleTextColourForHighlightedState: .blue, buttonTitleFontType: "Apple SD Gothic Neo", buttonTitleFontSize: 17, buttonBackgroundHexColourCode: "#B24B32", buttonFrameCornerRadius: 25, buttonFrameBorderWidth: 2, buttonFrameBorderColour: "#7AFFD2", buttonBackgroundTransperancyAlphaValue: 0.75, buttonTagNumber: 1, buttonTarget: self, buttonSelector: #selector(buttonPressed(sender:)), buttonImageForNormalState: "New File")
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("viewDidLayoutSubviews")
view.addSubview(newFileButton)
newFileButton.frame.size.width = 140
newFileButton.frame.size.height = 100
}
#objc func buttonPressed(sender : UIButton) {
if sender.tag == 1 {
newFileButton.shake()
guard let nextViewControllerToGoTo = storyboard?.instantiateViewController(withIdentifier: "NewFileButtonPressedTabController") else {
print("NewFileButtonPressedTabController could not be presented")
return
}
present(nextViewControllerToGoTo, animated: true, completion: nil)
}
}
However, as it can be seen from the above code, I ended up defining the size of the custom UIButton in terms of its width and height manually.
I wonder if there is a way for Xcode to figure out the best width and height for each custom UIButton based on its contents, mainly its title and image?
P.S: I tried to use intrinsicContent, however, it did not work out for me. Any suggestions are much appreciated.
Thanks,
Shadi.
Remove the code that sets newFileButton.frame.size.width and .height, and it should take on the content size of whatever titles/images you add to it - but only at a minimum. If you want it to be any bigger than that, just set content insets (sort of like padding around the content).
In your button's class:
self.contentEdgeInsets = UIEdgeInsets(top: 10, left: 30, bottom: 10, right: 20)
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -20, bottom: 0, right: 0)