UIKit - adding layer effect to button removes image - ios

I have a "fake" check box that I made using UIButton and:
var mCheckState: Bool = false {
didSet {
if mCheckState == true {
self.setImage(checkedImage, for: UIControl.State.normal)
} else {
self.setImage(uncheckedImage, for: UIControl.State.normal)
}
}
}
But when I create this layer style to add shadow/highlight edge:
contentHorizontalAlignment = .left;
contentEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0);
setTitleColor(.black, for: UIControl.State.normal)
setTitleColor(UIColor(red: 0.85, green: 0.70, blue: 0.65, alpha: 1.0), for: UIControl.State.highlighted)
layer.backgroundColor = globalColorCG_white_background
layer.shadowColor = CGColor(gray: 1.0, alpha: 1.0)
layer.shadowOpacity = 0.8
layer.shadowRadius = globalButtonRadiusShadow * 0.75
layer.cornerRadius = globalButtonRadiusShadow
layer.shadowOffset = CGSize(width: -6.0,height: -8.0)
layer2 = CALayer(layer: layer)
layer2!.backgroundColor = globalColorCG_white_background
layer2!.shadowColor = CGColor(gray: 0.0, alpha: 1.0)
layer2!.shadowOpacity = 0.1
layer2!.shadowRadius = globalButtonRadiusShadow * 0.5
layer2!.cornerRadius = globalButtonRadiusShadow
layer2!.shadowOffset = CGSize(width: 6.0, height: 8.0)
layer2!.frame = layer.bounds
layer.insertSublayer(layer2!, at: 0)
showsTouchWhenHighlighted = true
...the image no longer renders. What should I add/fix to get the image to render again? :- )
Extra code for info:
let checkedImage = UIImage(systemName: "checkmark.square")! as UIImage
let uncheckedImage = UIImage(systemName: "square")! as UIImage
var mIsCheckBox = false
var mCheckState: Bool = false {
didSet {
if mCheckState == true {
self.setImage(checkedImage, for: UIControl.State.normal)
//self.bringSubviewToFront(checkedImage)
} else {
self.setImage(uncheckedImage, for: UIControl.State.normal)
//self.bringSubviewToFront(uncheckedImage)
}
}
}

You can bring to front your image
class ButtonSubclass: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
/**
Set other button property and layers.
*/
if let imgView = imageView {
self.bringSubviewToFront(imgView)
}
}
}

You need to add your layer2 below the button's ImageView layer.
So change this line:
layer.insertSublayer(layer2!, at: 0)
To this:
layer.insertSublayer(layer2!, below: imageView!.layer)
You may want to check the button's imageView isn't nil and in cases where it is just add as you have already:
if let imageViewLayer = imageView?.layer {
layer.insertSublayer(layer2!, below: imageViewLayer)
} else {
layer.insertSublayer(layer2!, at: 0)
}

Related

How do you animate a floating label in swift

I'm trying to create a floating label similar to this: https://dribbble.com/shots/1254439--GIF-Float-Label-Form-Interaction
I've gotten down a decent amount of the actual functionality but I am having a problem with the actual animation part.
Here is my code:
import UIKit
class FloatingLabelInput: UITextField {
var floatingLabel: UILabel!// = UILabel(frame: CGRect.zero)
var floatingLabelHeight: CGFloat = 14
var button = UIButton(type: .custom)
var imageView = UIImageView(frame: CGRect.zero)
#IBInspectable
var _placeholder: String?
#IBInspectable
var floatingLabelColor: UIColor = UIColor.black {
didSet {
self.floatingLabel.textColor = floatingLabelColor
self.setNeedsDisplay()
}
}
#IBInspectable
var activeBorderColor: UIColor = UIColor.blue
#IBInspectable
var floatingLabelBackground: UIColor = Theme.current.backgroundColor {
didSet {
self.floatingLabel.backgroundColor = self.floatingLabelBackground
self.setNeedsDisplay()
}
}
#IBInspectable
var floatingLabelFont: UIFont = UIFont.systemFont(ofSize: 8) {
didSet {
self.floatingLabel.font = self.floatingLabelFont
self.font = self.floatingLabelFont
self.setNeedsDisplay()
}
}
#IBInspectable
var _backgroundColor: UIColor = Theme.current.backgroundColor {
didSet {
self.layer.backgroundColor = self._backgroundColor.cgColor
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self._placeholder = (self._placeholder != nil) ? self._placeholder : placeholder
placeholder = self._placeholder // Make sure the placeholder is shown
self.floatingLabel = UILabel(frame: CGRect.zero)
self.addTarget(self, action: #selector(self.addFloatingLabel), for: .editingDidBegin)
self.addTarget(self, action: #selector(self.removeFloatingLabel), for: .editingDidEnd)
self.addTarget(self, action: #selector(self.colorFloatingLabel), for: .editingDidBegin)
self.addTarget(self, action: #selector(self.removeColorFloatingLabel), for: .editingDidEnd)
}
#objc func colorFloatingLabel() {
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseInOut, animations: {
self.floatingLabel.textColor = UIColor.systemBlue
})
}
#objc func removeColorFloatingLabel() {
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseInOut, animations: {
self.floatingLabel.textColor = Theme.current.grays
})
}
// Add a floating label to the view on becoming first responder
#objc func addFloatingLabel() {
if self.text == "" {
UIView.animate(withDuration: 0.2, delay: 0.0, options: .transitionCurlUp, animations: {
self.floatingLabel.textColor = self.floatingLabelColor
self.floatingLabel.font = self.floatingLabelFont
self.floatingLabel.text = self._placeholder
self.floatingLabel.layer.backgroundColor = UIColor.white.cgColor
self.floatingLabel.translatesAutoresizingMaskIntoConstraints = false
self.floatingLabel.clipsToBounds = true
self.floatingLabel.textAlignment = .center
self.floatingLabel.frame = CGRect(x: 0, y: 0, width: self.floatingLabel.frame.width+4, height: self.floatingLabel.frame.height+2)
self.layer.borderColor = self.activeBorderColor.cgColor
self.addSubview(self.floatingLabel)
self.floatingLabel.bottomAnchor.constraint(equalTo: self.topAnchor, constant: -2).isActive = true // Place our label 10 pts above the text field
self.placeholder = ""
})
}
// Floating label may be stuck behind text input. we bring it forward as it was the last item added to the view heirachy
self.bringSubviewToFront(subviews.last!)
self.setNeedsDisplay()
}
#objc func removeFloatingLabel() {
if self.text == "" {
UIView.animate(withDuration: 0.2, delay: 0.0, options: .transitionCurlDown, animations: {
self.subviews.forEach{ $0.removeFromSuperview() }
self.setNeedsDisplay()
})
self.placeholder = self._placeholder
}
self.layer.borderColor = UIColor.black.cgColor
}
func addViewPasswordButton() {
self.button.setImage(UIImage(named: "ic_reveal"), for: .normal)
self.button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
self.button.frame = CGRect(x: 0, y: 16, width: 22, height: 16)
self.button.clipsToBounds = true
self.rightView = self.button
self.rightViewMode = .always
self.button.addTarget(self, action: #selector(self.enablePasswordVisibilityToggle), for: .touchUpInside)
}
func addImage(image: UIImage){
self.imageView.image = image
self.imageView.frame = CGRect(x: 20, y: 0, width: 20, height: 20)
self.imageView.translatesAutoresizingMaskIntoConstraints = true
self.imageView.contentMode = .scaleAspectFit
self.imageView.clipsToBounds = true
DispatchQueue.main.async {
self.leftView = self.imageView
self.leftViewMode = .always
}
}
#objc func enablePasswordVisibilityToggle() {
isSecureTextEntry.toggle()
if isSecureTextEntry {
self.button.setImage(UIImage(named: "ic_show"), for: .normal)
}else{
self.button.setImage(UIImage(named: "ic_hide"), for: .normal)
}
}
}
As you can see here, I tried UIView.animate but all that does is move it downwards as shown here (I changed the duration of the UIView.animate to make the animation really visible): https://m.imgur.com/a/UxSLqBK
Instead, I want it to animate like this: https://github.com/Skyscanner/SkyFloatingLabelTextField/blob/master/SkyFloatingLabelTextField/images/example-1.gif

UIButton with background image, rounded corners and a shadow

I'm trying to create a UIButton with rounded corners, a background image and a shadow. Before adding the shadow, everything works fine.
But after adding shadow values, the shadow doesn't show up. Obviously due to clipsToBounds property value being set to true. If I remove that, it looks like this.
Since I need the corner radius as well, I cannot have the clipsToBounds be false.
This is my code.
class CustomButton: UIButton {
var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
clipsToBounds = true
}
}
var shadowRadius: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue
}
}
var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
var shadowOffset: CGSize {
get {
return layer.shadowOffset
}
set {
layer.shadowOffset = newValue
}
}
var shadowColor: UIColor? {
get {
if let color = layer.shadowColor {
return UIColor(cgColor: color)
}
return nil
}
set {
if let color = newValue {
layer.shadowColor = color.cgColor
} else {
layer.shadowColor = nil
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
private lazy var button: CustomButton = {
let button = CustomButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setBackgroundImage(UIImage(named: "Rectangle"), for: .normal)
button.setTitleColor(.white, for: .normal)
button.setTitle("Sign Up", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
button.cornerRadius = 20
button.shadowColor = .systemGreen
button.shadowRadius = 10
button.shadowOpacity = 1
button.shadowOffset = CGSize(width: 0, height: 0)
return button
}()
Is there a workaround to have both the shadow and the corner radius?
Demo project
You can do it via adding shadow and background image with different layer.
First, if you don't need the properties, remove all and modify your CustomButton implementation just like below (modify as your need):
class CustomButton: UIButton {
private let cornerRadius: CGFloat = 20
private var imageLayer: CALayer!
private var shadowLayer: CALayer!
override func draw(_ rect: CGRect) {
addShadowsLayers(rect)
}
private func addShadowsLayers(_ rect: CGRect) {
// Add Image
if self.imageLayer == nil {
let imageLayer = CALayer()
imageLayer.frame = rect
imageLayer.contents = UIImage(named: "Rectangle")?.cgImage
imageLayer.cornerRadius = cornerRadius
imageLayer.masksToBounds = true
layer.insertSublayer(imageLayer, at: 0)
self.imageLayer = imageLayer
}
// Set the shadow
if self.shadowLayer == nil {
let shadowLayer = CALayer()
shadowLayer.masksToBounds = false
shadowLayer.shadowColor = UIColor.systemGreen.cgColor
shadowLayer.shadowOffset = .zero
shadowLayer.shadowOpacity = 1
shadowLayer.shadowRadius = 10
shadowLayer.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
layer.insertSublayer(shadowLayer, at: 0)
self.shadowLayer = shadowLayer
}
}
}
And initialize your button like below:
private lazy var button: CustomButton = {
let button = CustomButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(.white, for: .normal)
button.setTitle("Sign Up", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
return button
}()
You need to use two separate views for shadow and image. I can't find any solution to set image, shadow, and corner radius using the same button layer.
Make button's corner radius(clipsToBounds=true) rounded and set the image on it.
Take a shadow view under the button with proper shadow radius and offset.
You can add view.layer.masksToBounds = false
This will disable clipping for sublayer
https://developer.apple.com/documentation/quartzcore/calayer/1410896-maskstobounds

Swift: Push Objects under the Label according to the number of lines

I'm trying to make the same behavior of the Material design textfield with a custom textfield.
I created a class that inherits from textfield and every thing is working fine. The only problem is in one scenario. when I have an object under the textfield, and i add the error label under the text field. the error label might be more than one line. so it overlays the object under the textfield. However, in the material design library, the objects under the textfield are automatically pushed down according ton the number of lines of the error label.
here is my custom textfield code:
import UIKit
import RxSwift
import RxCocoa
class FloatingTextField2: UITextField {
var placeholderLabel: UILabel!
var line: UIView!
var errorLabel: UILabel!
let bag = DisposeBag()
var activeColor = Constants.colorBlue
var inActiveColor = UIColor(red: 84/255.0, green: 110/255.0, blue: 122/255.0, alpha: 0.8)
var errorColorFull = UIColor(red: 254/255.0, green: 103/255.0, blue: 103/255.0, alpha: 1.0)
//var errorColorParcial = UIColor(red: 254/255.0, green: 103/255.0, blue: 103/255.0, alpha: 0.5)
private var lineYPosition: CGFloat!
private var lineXPosition: CGFloat!
private var lineWidth: CGFloat!
private var lineHeight: CGFloat!
private var errorLabelYPosition: CGFloat!
private var errorLabelXPosition: CGFloat!
private var errorLabelWidth: CGFloat!
private var errorLabelHeight: CGFloat!
var maxFontSize: CGFloat = 14
var minFontSize: CGFloat = 11
let errorLabelFont = UIFont(name: "Lato-Regular", size: 12)
var animationDuration = 0.35
var placeholderText: String = "" {
didSet {
if placeholderLabel != nil {
placeholderLabel.text = placeholderText
}
}
}
var isTextEntrySecured: Bool = false {
didSet {
self.isSecureTextEntry = isTextEntrySecured
}
}
override func draw(_ rect: CGRect) {
//setUpUI()
}
override func awakeFromNib() {
setUpUI()
}
func setUpUI() {
if placeholderLabel == nil {
placeholderLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: 20))
self.addSubview(placeholderLabel)
self.borderStyle = .none
placeholderLabel.text = "Placeholder Preview"
placeholderLabel.textColor = inActiveColor
self.font = UIFont(name: "Lato-Regular", size: maxFontSize)
self.placeholderLabel.font = UIFont(name: "Lato-Regular", size: maxFontSize)
self.placeholder = ""
self.textColor = .black
setUpTextField()
}
if line == nil {
lineYPosition = self.frame.height
lineXPosition = -16
lineWidth = self.frame.width + 32
lineHeight = 1
line = UIView(frame: CGRect(x: lineXPosition, y: lineYPosition, width: lineWidth, height: lineHeight))
self.addSubview(line)
line.backgroundColor = inActiveColor
}
if errorLabel == nil {
errorLabelYPosition = lineYPosition + 8
errorLabelXPosition = 0
errorLabelWidth = self.frame.width
errorLabelHeight = calculateErrorLabelHeight(text: "")
errorLabel = UILabel(frame: CGRect(x: 0, y: errorLabelYPosition, width: errorLabelWidth, height: errorLabelHeight))
self.addSubview(errorLabel)
errorLabel.numberOfLines = 0
errorLabel.textColor = errorColorFull
errorLabel.text = ""
errorLabel.font = errorLabelFont
sizeToFit()
}
}
func setUpTextField(){
self.rx.controlEvent(.editingDidBegin).subscribe(onNext: { (next) in
if self.text?.isEmpty ?? false {
self.animatePlaceholderUp()
}
}).disposed(by: bag)
self.rx.controlEvent(.editingDidEnd).subscribe(onNext: { (next) in
if self.text?.isEmpty ?? false {
self.animatePlaceholderCenter()
}
}).disposed(by: bag)
}
func setErrorText(_ error: String?, errorAccessibilityValue: String?) {
if let errorText = error {
self.resignFirstResponder()
errorLabelHeight = calculateErrorLabelHeight(text: errorText)
self.errorLabel.frame = CGRect(x: 0, y: errorLabelYPosition, width: errorLabelWidth, height: errorLabelHeight)
self.errorLabel.text = errorText
self.errorLabel.isHidden = false
self.line.backgroundColor = errorColorFull
}else{
self.errorLabel.text = ""
self.errorLabel.isHidden = true
}
errorLabel.accessibilityIdentifier = errorAccessibilityValue ?? "textinput_error"
}
func animatePlaceholderUp(){
UIView.animate(withDuration: animationDuration, animations: {
self.line.frame.size.height = 2
self.line.backgroundColor = self.activeColor
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.minFontSize)
self.placeholderLabel.textColor = self.activeColor
self.placeholderLabel.frame = CGRect(x: 0, y: (self.frame.height/2 + 8) * -1, width: self.frame.width, height: self.frame.height)
self.layoutIfNeeded()
}) { (done) in
}
}
func animatePlaceholderCenter(){
UIView.animate(withDuration: animationDuration, animations: {
self.line.frame.size.height = 1
self.line.backgroundColor = self.inActiveColor
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.maxFontSize)
self.placeholderLabel.textColor = self.inActiveColor
self.placeholderLabel.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
self.layoutIfNeeded()
}) { (done) in
}
}
func calculateErrorLabelHeight(text:String) -> CGFloat{
let font = errorLabelFont
let width = self.frame.width
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
}
How can I solve this problem? I could not find anything on stack overflow or google related to my problem.
As mentioned in the comments:
You'll be much better off using constraints rather than explicit frames
Adding subviews to a UITextField will show them outside the Bounds of the field, meaning they won't affect the frame (and thus the constraints)
If the constraints are set properly, they will control the "containing view" height
The key to getting your "error" label to expand the view is to apply multiple vertical constraints, and activate / deactivate as needed.
Here is a complete example of a custom UIView which contains a text field, a placeholder label and an error label. The example view controller includes "demo" buttons to show the capabilities.
I suggest you add this code and try it out. If it suits your needs, there are plenty of comments in it that you should be able to tweak fonts, spacing, etc to your liking.
Or, it should at least give you some ideas of how to set up your own.
FloatingTextFieldView - UIView subclass
class FloatingTextFieldView: UIView, UITextFieldDelegate {
var placeHolderTopConstraint: NSLayoutConstraint!
var placeHolderCenterYConstraint: NSLayoutConstraint!
var placeHolderLeadingConstraint: NSLayoutConstraint!
var lineHeightConstraint: NSLayoutConstraint!
var errorLabelBottomConstraint: NSLayoutConstraint!
var activeColor: UIColor = UIColor.blue
var inActiveColor: UIColor = UIColor(red: 84/255.0, green: 110/255.0, blue: 122/255.0, alpha: 0.8)
var errorColorFull: UIColor = UIColor(red: 254/255.0, green: 103/255.0, blue: 103/255.0, alpha: 1.0)
var animationDuration = 0.35
var maxFontSize: CGFloat = 14
var minFontSize: CGFloat = 11
let errorLabelFont = UIFont(name: "Lato-Regular", size: 12)
let placeholderLabel: UILabel = {
let v = UILabel()
v.text = "Default Placeholder"
v.setContentHuggingPriority(.required, for: .vertical)
return v
}()
let line: UIView = {
let v = UIView()
v.backgroundColor = .lightGray
return v
}()
let errorLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.text = "Default Error"
v.setContentCompressionResistancePriority(.required, for: .vertical)
return v
}()
let textField: UITextField = {
let v = UITextField()
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
clipsToBounds = true
backgroundColor = .white
[textField, line, placeholderLabel, errorLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
// place holder label gets 2 vertical constraints
// top of view
// centerY to text field
placeHolderTopConstraint = placeholderLabel.topAnchor.constraint(equalTo: topAnchor, constant: 0.0)
placeHolderCenterYConstraint = placeholderLabel.centerYAnchor.constraint(equalTo: textField.centerYAnchor, constant: 0.0)
// place holder leading constraint is 16-pts (when centered on text field)
// when animated above text field, we'll change the constant to 0
placeHolderLeadingConstraint = placeholderLabel.leadingAnchor.constraint(equalTo: textField.leadingAnchor, constant: 16.0)
// error label bottom constrained to bottom of view
// will be activated when shown, deactivated when hidden
errorLabelBottomConstraint = errorLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0)
// line height constraint constant changes between 1 and 2 (inactive / active)
lineHeightConstraint = line.heightAnchor.constraint(equalToConstant: 1.0)
NSLayoutConstraint.activate([
// text field top 16-pts from top of view
// leading and trailing = 0
textField.topAnchor.constraint(equalTo: topAnchor, constant: 16.0),
textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
textField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// text field height = 24
textField.heightAnchor.constraint(equalToConstant: 24.0),
// text field bottom is AT LEAST 4 pts
textField.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -4.0),
// line view top is 2-pts below text field bottom
// leading and trailing = 0
line.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 2.0),
line.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
line.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// error label top is 4-pts from text field bottom
// leading and trailing = 0
errorLabel.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 4.0),
errorLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
errorLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
placeHolderCenterYConstraint,
placeHolderLeadingConstraint,
lineHeightConstraint,
])
// I'm not using Rx, so set the delegate
textField.delegate = self
textField.font = UIFont(name: "Lato-Regular", size: maxFontSize)
textField.textColor = .black
placeholderLabel.font = UIFont(name: "Lato-Regular", size: maxFontSize)
placeholderLabel.textColor = inActiveColor
line.backgroundColor = inActiveColor
errorLabel.textColor = errorColorFull
errorLabel.font = errorLabelFont
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField.text?.isEmpty ?? false {
self.animatePlaceholderUp()
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.text?.isEmpty ?? false {
self.animatePlaceholderCenter()
}
}
func animatePlaceholderUp() -> Void {
UIView.animate(withDuration: animationDuration, animations: {
// increase line height
self.lineHeightConstraint.constant = 2.0
// set line to activeColor
self.line.backgroundColor = self.activeColor
// set placeholder label font and color
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.minFontSize)
self.placeholderLabel.textColor = self.activeColor
// deactivate placeholder label CenterY constraint
self.placeHolderCenterYConstraint.isActive = false
// activate placeholder label Top constraint
self.placeHolderTopConstraint.isActive = true
// move placeholder label leading to 0
self.placeHolderLeadingConstraint.constant = 0
self.layoutIfNeeded()
}) { (done) in
}
}
func animatePlaceholderCenter() -> Void {
UIView.animate(withDuration: animationDuration, animations: {
// decrease line height
self.lineHeightConstraint.constant = 1.0
// set line to inactiveColor
self.line.backgroundColor = self.inActiveColor
// set placeholder label font and color
self.placeholderLabel.font = self.placeholderLabel.font.withSize(self.maxFontSize)
self.placeholderLabel.textColor = self.inActiveColor
// deactivate placeholder label Top constraint
self.placeHolderTopConstraint.isActive = false
// activate placeholder label CenterY constraint
self.placeHolderCenterYConstraint.isActive = true
// move placeholder label leading to 16
self.placeHolderLeadingConstraint.constant = 16
self.layoutIfNeeded()
}) { (done) in
}
}
func setErrorText(_ error: String?, errorAccessibilityValue: String?, endEditing: Bool) {
if let errorText = error {
UIView.animate(withDuration: 0.05, animations: {
self.errorLabel.text = errorText
self.line.backgroundColor = self.errorColorFull
self.errorLabel.isHidden = false
// activate error label Bottom constraint
self.errorLabelBottomConstraint.isActive = true
}) { (done) in
if endEditing {
self.textField.resignFirstResponder()
}
}
}else{
UIView.animate(withDuration: 0.05, animations: {
self.errorLabel.text = ""
self.line.backgroundColor = self.inActiveColor
self.errorLabel.isHidden = true
// deactivate error label Bottom constraint
self.errorLabelBottomConstraint.isActive = false
}) { (done) in
if endEditing {
self.textField.resignFirstResponder()
}
}
}
errorLabel.accessibilityIdentifier = errorAccessibilityValue ?? "textinput_error"
}
// func to set / clear element background colors
// to make it easy to see the frames
func showHideFrames(show b: Bool) -> Void {
if b {
self.backgroundColor = UIColor(red: 0.8, green: 0.8, blue: 1.0, alpha: 1.0)
placeholderLabel.backgroundColor = .cyan
errorLabel.backgroundColor = .green
textField.backgroundColor = .yellow
} else {
self.backgroundColor = .white
[placeholderLabel, errorLabel, textField].forEach {
$0.backgroundColor = .clear
}
}
}
}
DemoFLoatingTextViewController
class DemoFLoatingTextViewController: UIViewController {
// FloatingTextFieldView
let sampleFTF: FloatingTextFieldView = {
let v = FloatingTextFieldView()
return v
}()
// a label to constrain below the FloatingTextFieldView
// so we can see it gets "pushed down"
let demoLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.text = "This is a label outside the Floating Text Field. As you will see, it gets \"pushed down\" when the error label is shown."
v.backgroundColor = .brown
v.textColor = .yellow
return v
}()
// buttons to Demo the functionality
let btnA: UIButton = {
let b = UIButton(type: .system)
b.setTitle("End Editing", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnB: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Set Error", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnC: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Clear Error", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnD: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Set & End", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnE: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Clear & End", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnF: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Show Frames", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let btnG: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Hide Frames", for: .normal)
b.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return b
}()
let errorMessages: [String] = [
"Simple Error",
"This will end up being a Multiline Error message. It is long enough to cause word wrapping."
]
var errorCount: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// add Demo buttons
let btnStack = UIStackView()
btnStack.axis = .vertical
btnStack.spacing = 6
btnStack.translatesAutoresizingMaskIntoConstraints = false
[[btnA], [btnB, btnC], [btnD, btnE], [btnF, btnG]].forEach { btns in
let sv = UIStackView()
sv.distribution = .fillEqually
sv.spacing = 12
sv.translatesAutoresizingMaskIntoConstraints = false
btns.forEach {
sv.addArrangedSubview($0)
}
btnStack.addArrangedSubview(sv)
}
view.addSubview(btnStack)
// add FloatingTextFieldView and demo label
view.addSubview(sampleFTF)
view.addSubview(demoLabel)
sampleFTF.translatesAutoresizingMaskIntoConstraints = false
demoLabel.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// buttons stack Top = 20, centerX, width = 80% of view width
btnStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
btnStack.centerXAnchor.constraint(equalTo: g.centerXAnchor),
btnStack.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.8),
// FloatingTextFieldView Top = 40-pts below buttons stack
sampleFTF.topAnchor.constraint(equalTo: btnStack.bottomAnchor, constant: 40.0),
// FloatingTextFieldView Leading = 60-pts
sampleFTF.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
// FloatingTextFieldView width = 240
sampleFTF.widthAnchor.constraint(equalToConstant: 240.0),
// Note: we are not setting the FloatingTextFieldView Height!
// constrain demo label Top = 8-pts below FloatingTextFieldView bottom
demoLabel.topAnchor.constraint(equalTo: sampleFTF.bottomAnchor, constant: 8.0),
// Leading = FloatingTextFieldView Leading
demoLabel.leadingAnchor.constraint(equalTo: sampleFTF.leadingAnchor),
// Width = 200
demoLabel.widthAnchor.constraint(equalToConstant: 200.0),
])
// add touchUpInside targets for demo buttons
btnA.addTarget(self, action: #selector(endEditing(_:)), for: .touchUpInside)
btnB.addTarget(self, action: #selector(setError(_:)), for: .touchUpInside)
btnC.addTarget(self, action: #selector(clearError(_:)), for: .touchUpInside)
btnD.addTarget(self, action: #selector(setAndEnd(_:)), for: .touchUpInside)
btnE.addTarget(self, action: #selector(clearAndEnd(_:)), for: .touchUpInside)
btnF.addTarget(self, action: #selector(showFrames(_:)), for: .touchUpInside)
btnG.addTarget(self, action: #selector(hideFrames(_:)), for: .touchUpInside)
}
#objc func endEditing(_ sender: Any) -> Void {
sampleFTF.textField.resignFirstResponder()
}
#objc func setError(_ sender: Any) -> Void {
sampleFTF.setErrorText(errorMessages[errorCount % 2], errorAccessibilityValue: "", endEditing: false)
errorCount += 1
}
#objc func clearError(_ sender: Any) -> Void {
sampleFTF.setErrorText(nil, errorAccessibilityValue: "", endEditing: false)
}
#objc func setAndEnd(_ sender: Any) -> Void {
sampleFTF.setErrorText(errorMessages[errorCount % 2], errorAccessibilityValue: "", endEditing: true)
errorCount += 1
}
#objc func clearAndEnd(_ sender: Any) -> Void {
sampleFTF.setErrorText(nil, errorAccessibilityValue: "", endEditing: true)
}
#objc func showFrames(_ sender: Any) -> Void {
sampleFTF.showHideFrames(show: true)
}
#objc func hideFrames(_ sender: Any) -> Void {
sampleFTF.showHideFrames(show: false)
}
}
Example results:

swift can't click uibutton located inside multiple subviews

I am not getting when I press the arrowButton, not even the print("toggleWhiteMenu pressed") is printing in the console? I have searched the other questions like this one but the don't seem to help.
let card: UIView = {
let tsl = UIView()
tsl.backgroundColor = UIColor.black
tsl.alpha = 0.9
return tsl
}()
let yellowCard: UIView = {
let tsl = UIView()
tsl.backgroundColor = UIColor.yellow
//tsl.alpha = 1
tsl.isUserInteractionEnabled = false
return tsl
}()
lazy var arrowButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(#imageLiteral(resourceName: "down arrow"), for: .normal)
button.tintColor = UIColor.black
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
button.addTarget(self, action: #selector(self.toggleWhiteMenu), for: .touchUpInside)
return button
}()
func toggleWhiteMenu() {
print("toggleWhiteMenu pressed")
UIView.animate(withDuration: 1, animations: {
self.whiteMenu.transform = CGAffineTransform(scaleX: 11, y: 11)
}) { (true) in
print("really sick")
}
}
let whiteMenu: UIView = {
let tsl = UIView()
tsl.backgroundColor = UIColor.white
tsl.alpha = 1
tsl.isUserInteractionEnabled = false
return tsl
}()
let containerView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .yellow
v.isUserInteractionEnabled = false
return v
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
return v
}()
let backgroundImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "eiffel tower")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.isUserInteractionEnabled = false
imageView.layer.masksToBounds = true
imageView.contentMode = .scaleAspectFill
return imageView
}()
func setUpViews() {
self.view.addSubview(scrollView)
scrollView.addSubview(containerView)
containerView.addSubview(backgroundImageView)
backgroundImageView.addSubview(card)
backgroundImageView.addSubview(yellowCard)
yellowCard.addSubview(whiteMenu)
yellowCard.addSubview(arrowButton)
///i have left out constraints except for the arrowbutton's constraints as i don't deem them necessary for this question
arrowButton.anchor(top: nil, left: nil, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: view.frame.width/6, height: view.frame.width/6)
arrowButton.clipsToBounds = true
arrowButton.centerXAnchor.constraint(equalTo: whiteMenu.centerXAnchor).isActive = true
arrowButton.centerYAnchor.constraint(equalTo: whiteMenu.centerYAnchor).isActive = true
}
Please add #objc with selector method like below.
#objc func toggleWhiteMenu() {
print("toggleWhiteMenu pressed")
UIView.animate(withDuration: 1, animations: {
self.whiteMenu.transform = CGAffineTransform(scaleX: 11, y: 11)
}) { (true) in
print("really sick")
}
}
You are setting:
.isUserInteractionEnabled = false
on multiple views, and then adding a button as a subview. isUserInteractionEnabled set to false on a parent view will cascade down to all subviews, including buttons and other controls.

storyboard.instantiateViewController executes for about 3 seconds

I noticed that my app has a delay before presenting UIViewController, so I used print() to find out which line of code causing it and found up that let navigationVC = storyboard!.instantiateViewController(withIdentifier: "filterNavigationVC") as! UINavigationController executes for about 2 seconds. Why might it be like that? I've tried to remove everything except this one without even navigating and still it took that long.
#IBAction func filter(_ sender: AnyObject) {
let navigationVC = storyboard!.instantiateViewController(withIdentifier: "filterNavigationVC") as! UINavigationController
let destinationVC = navigationVC.topViewController as! FilterViewController
navigationVC.modalPresentationStyle = .overCurrentContext
destinationVC.backgroundColorValue = self.view.backgroundColor!
destinationVC.color = themeColor.light
destinationVC.delegate = self
destinationVC.billModel = self.price
destinationVC.applyedFilter = self.applyedFilter
self.present(navigationVC, animated: true, completion: nil)
}
Next UIViewController's viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
customization()
}
fileprivate func customization(){
setupBillButtons(billModel.range["low"]!, medium: self.billModel.range["medium"]!, high: self.billModel.range["high"]!)
tempConfigButtons([self.lowBillButton, self.mediumBillButton, self.highBillButton])
if color == themeColor.light {
customizeLightTheme()
} else if color == themeColor.dark {
customizeDarkTheme()
}
setCharacterSpacingForLabels([typeLabel, kitchenLabel, avarageBill, metroLabel, tagsLabel], spacing: 2.79)
setSeparatorInsets([kitchenTableView, metroTableView, typeTableView], edge: UIEdgeInsetsMake(0, 20, 0, 20))
backgroundVoew.backgroundColor = backgroundColorValue
backgroundVoew.addBackroundBlur()
self.navigationController?.navigationBar.isHidden = true
}
fileprivate func customizeDarkTheme() {
comfirmButton.setImage(UIImage(named: "comfirmLight"), for: UIControlState())
dismissButton.setImage(UIImage(named: "closeLight"), for: UIControlState())
setColorsForTableViews([kitchenTableView, metroTableView, typeTableView], separatorColor: colorWithAlpha(whiteColor, alpha: 0.05), tintColor: colorWithAlpha(whiteColor, alpha: 0.5))
setColorForLabels([kitchenLabel, avarageBill, metroLabel, typeLabel, tagsLabel], color: colorWithAlpha(whiteColor, alpha: 0.50))
topName.textColor = whiteColor
UIApplication.shared.statusBarStyle = UIStatusBarStyle.lightContent
self.view.backgroundColor = backgroundColorValue
bottomComfitmButton.tintColor = whiteColor
bottomComfitmButton.backgroundColor = colorWithAlpha(whiteColor, alpha: 0.15)
showAllTags.titleLabel!.addCharactersSpacing(-0.39, text: (self.showAllTags.titleLabel?.text)!)
showAllTags.setTitleColor(colorWithAlpha(whiteColor, alpha: 0.5), for: UIControlState())
showAllFilterImage.image = UIImage(named: "filterShowAllDark")
}
fileprivate func customizeLightTheme() {
comfirmButton.setImage(UIImage(named: "comfirmDark"), for: UIControlState())
dismissButton.setImage(UIImage(named: "closeDark"), for: UIControlState())
setColorForLabels([kitchenLabel, avarageBill, metroLabel, typeLabel, tagsLabel], color: colorWithAlpha(grayColor, alpha: 0.50))
topName.textColor = grayColor
setColorsForTableViews([kitchenTableView, metroTableView, typeTableView], separatorColor: colorWithAlpha(grayColor, alpha: 0.1), tintColor: colorWithAlpha(grayColor, alpha: 0.59))
UIApplication.shared.statusBarStyle = UIStatusBarStyle.default
bottomComfitmButton.tintColor = grayColor
bottomComfitmButton.backgroundColor = colorWithAlpha(whiteColor, alpha: 0.25)
showAllTags.titleLabel!.addCharactersSpacing(-0.39, text: (self.showAllTags.titleLabel?.text)!)
showAllTags.setTitleColor(colorWithAlpha(grayColor, alpha: 0.5), for: UIControlState())
showAllFilterImage.image = UIImage(named: "filterShowAllLight")
}
fileprivate func setupBillButtons(_ low: Bool, medium: Bool, high: Bool) {
lowBillButton.isSelected = low
mediumBillButton.isSelected = medium
highBillButton.isSelected = high
}
fileprivate func setSeparatorInsets(_ tableViews: [UITableView], edge: UIEdgeInsets) {
for tableView in tableViews {
tableView.separatorInset = edge
}
}
fileprivate func tempConfigButtons(_ buttons: [UIButton]) {
for button in buttons {
button.setTitleColor(whiteColor, for: .selected)
if self.color == themeColor.dark {
button.setBackgroundColor(colorWithAlpha(whiteColor, alpha: 0.50), forState: .selected)
button.setTitleColor(whiteColor, for: UIControlState())
DispatchQueue.main.async(execute: {
button.roundCorners([.topLeft, .topRight, .bottomLeft, .bottomRight], radius: 15, borderColor: self.colorWithAlpha(self.whiteColor, alpha: 0.50), borderWidth: 1)
})
} else {
button.setTitleColor(grayColor, for: UIControlState())
button.setBackgroundColor(colorWithAlpha(grayColor, alpha: 0.50), forState: .selected)
DispatchQueue.main.async(execute: {
button.roundCorners([.topLeft, .topRight, .bottomLeft, .bottomRight], radius: 15, borderColor: self.grayColor, borderWidth: 1)
})
}
}
}

Resources