UIButton in Collection View Cell not receiving Touch Up Inside event - ios

I am trying to setup a Carousel-like UI with buttons in each carousel cell. I am using a Collection View to do this. The buttons show up in the cell, but do not seem to respond to the touch up inside event which should run tapped() and return "TAPPED!". Here is the code for the Collection View Cell which includes the button. Thanks for the help!
CarouselCollectionViewCell.swift
import UIKit
import Foundation
class CarouselCollectionViewCell: UICollectionViewCell {
static let identifier = "CarouselCollectionViewCell"
#objc func tapped() {
print("TAPPED!")
}
var mainView : UIView = {
var mainCellView = UIView()
mainCellView.translatesAutoresizingMaskIntoConstraints = false
return mainCellView
}()
var remindButton : UIButton = {
var textView = UIButton()
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(mainView)
mainView.addSubview(remindButton)
mainView.backgroundColor = .white
//Style
mainView.layer.cornerRadius = 8
mainView.layer.masksToBounds = true
// Constraints
mainView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
mainView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
mainView.heightAnchor.constraint(equalToConstant: 360).isActive = true
mainView.widthAnchor.constraint(equalToConstant: 290).isActive = true
self.remindButton.backgroundColor = UIColor(red: 69.0/255.0, green: 198.0/255.0, blue: 255.0/255.0, alpha: 1.0)
self.remindButton.layer.cornerRadius = 20
self.remindButton.layer.masksToBounds = true
self.remindButton.bottomAnchor.constraint(equalTo: self.mainView.bottomAnchor, constant: -30).isActive = true
self.remindButton.centerXAnchor.constraint(equalTo: self.mainView.centerXAnchor).isActive = true
self.remindButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
self.remindButton.widthAnchor.constraint(equalToConstant: 175).isActive = true
self.remindButton.setTitle("Add Reminder", for: .normal)
self.remindButton.addTarget(self, action: #selector(self.tapped), for: .touchUpInside)
}
override func layoutSubviews() {
super.layoutSubviews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Related

addSubview doesn't work in UIView - Swift Programmatically

I need to add a UIButton as a subview on a UIView but it actually doesn't appear at runtime.
This is my code:
let moreButton : UIButton = {
let button = UIButton()
button.setImage(#imageLiteral(resourceName: "more"), for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(moreButton)
moreButton.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
moreButton.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
moreButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
moreButton.widthAnchor.constraint(equalToConstant: 20).isActive = true
}
The button isn't added eventually to the view. I'm sure this is an easy fix but I can't wrap my head around it.
First of all, make sure you the viewController is showing anything since you're doing it without storyboards, check out this simple tutorial:
https://medium.com/better-programming/creating-a-project-without-storyboard-in-2020-and-without-swifui-82080eb6d13b
If the problem is with that UIButton, try to set up the subviews in viewDidLoad:
final class ViewController: UIViewController {
let cardView = CardView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(cardView)
/// Constraints
let margins = view.layoutMarginsGuide
cardView.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
cardView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
cardView.topAnchor.constraint(equalTo: margins.topAnchor).isActive = true
cardView.heightAnchor.constraint(equalToConstant: 30).isActive = true
cardView.widthAnchor.constraint(equalToConstant: 20).isActive = true
}
}
final class CardView: UIView {
let moreButton : UIButton = {
let button = UIButton()
button.setTitle("Button title", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override init(frame: CGRect) {
super.init(frame:frame)
self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(moreButton)
/// Constraints
let margins = self.layoutMarginsGuide
moreButton.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
moreButton.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
moreButton.topAnchor.constraint(equalTo: margins.topAnchor).isActive = true
moreButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
moreButton.widthAnchor.constraint(equalToConstant: 20).isActive = true
}
}
I've set up margins for the constraints and also added a leadingAnchor constraint, that might have been the issue as well.

Custom button not appearing in my viewController after importing it

I'm trying to create a custom button and import in my viewController to reduce the code inside it. However, it is not working. Could anyone give me any hint?
Despite the class ButtonView was instantiated properly, the button not show.
ButtonView.swift
import UIKit
final class ButtonView: UIButton {
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
setupLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Private functions
private func setupLayout() {
setTitle("Log In", for: .normal)
titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
setTitleColor(.white, for: .normal)
backgroundColor = UIColor(red: 94/255, green: 162/255, blue: 58/255, alpha: 1)
layer.cornerRadius = 10
translatesAutoresizingMaskIntoConstraints = false
}
}
HomeController.swift
class HomeController: UIViewController {
//MARK: - Properties
var delegate: ViewControllerDelegate?
//MARK: - Views
private let button = ButtonView()
//MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
configureNavigationBar()
}
fileprivate func setupLayout() {
view.backgroundColor = .white
view.addSubview(button)
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = false
}
...
Your constraint that positions the button vertically is not active, change it to true:
button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100).isActive = true

What error am I making when trying to pass data between ViewControllers using closures?

My main View Controller has an embedded UITabbarController in it. There are 2 subVCs: viewControllerONE & viewControllerTWO.
viewControllerONE displays a label showing the user's current score, and viewControllerTWO displays a button, pressing which should display an error window on viewControllerONE.
(For the sake of simplicity, the user has to manually navigate to viewControllerONE using the tab bar to see the error, ie, pressing the button on viewControllerTWO doesn't take you to viewControllerONE and then display the errorWindow.)
The error window is a simple UIView class.
From SO, I have learnt that the best way to pass data in swift isn't delegates but closures, as the former is more of an objective C design, and so I've used closures to pass data between view controllers.
So here is my viewControllerONE code:
class ViewControllerONE: UIViewController {
var score = 10
lazy var scoreLabel: UILabel = {
let label = UILabel()
label.text = String(score)
label.font = .systemFont(ofSize: 80)
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scoreLabel)
let VC = ViewControllerTWO()
VC.callback = { [weak self ] in
let error = errorView()
self?.view.addSubview(error)
}
NSLayoutConstraint.activate([
scoreLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
scoreLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
}
and here is my viewControllerTWO code:
class ViewControllerTWO: UIViewController {
var callback: (() -> Void)?
let buttonTwo: UIButton = {
let button = UIButton()
button.setTitle("HIT THIS BUTTON!", for: .normal)
button.backgroundColor = .systemBlue
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(buttonTwoPressed), for: .touchUpInside)
button.layer.cornerRadius = 8
return button
}()
#objc func buttonTwoPressed() {
print("PRESSEDDDD")
callback?()
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(buttonTwo)
NSLayoutConstraint.activate([
buttonTwo.centerXAnchor.constraint(equalTo: view.centerXAnchor),
buttonTwo.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
And here is the error view:
class ErrorView: UIView {
fileprivate let dismissButton: UIButton = {
let button = UIButton()
button.setTitle("DISMISS!!!!", for: .normal)
button.backgroundColor = .systemBlue
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(dismissButtonPressed), for: .touchUpInside)
button.layer.cornerRadius = 12
return button
}()
#objc func dismissButtonPressed() {
self.errorGoAway()
}
fileprivate let errorViewBox: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
v.layer.cornerRadius = 24
return v
}()
#objc fileprivate func errorGoAway() {
self.alpha = 0
}
#objc fileprivate func errorShow() {
self.alpha = 1
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(errorGoAway)))
self.backgroundColor = UIColor.gray
self.backgroundColor?.withAlphaComponent(0.8)
self.frame = UIScreen.main.bounds
self.addSubview(errorViewBox)
errorViewBox.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
errorViewBox.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
errorViewBox.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.7).isActive = true
errorViewBox.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.45).isActive = true
errorViewBox.addSubview(dismissButton)
dismissButton.leadingAnchor.constraint(equalTo: errorViewBox.leadingAnchor).isActive = true
dismissButton.trailingAnchor.constraint(equalTo: errorViewBox.trailingAnchor).isActive = true
dismissButton.centerYAnchor.constraint(equalTo: errorViewBox.centerYAnchor).isActive = true
dismissButton.heightAnchor.constraint(equalTo: errorViewBox.heightAnchor, multiplier: 0.15).isActive = true
errorShow()
}
You need to get your controller from tabBarController instead of create new instance:
class ViewControllerONE: UIViewController {
var score = 10
lazy var scoreLabel: UILabel = {
let label = UILabel()
label.text = String(score)
label.font = .systemFont(ofSize: 80)
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.textColor = .blue
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scoreLabel)
var VC: ViewControllerTWO?
let arrayOfVC = self.tabBarController?.viewControllers ?? []
for item in arrayOfVC {
if let secondVC = item as? ViewControllerTWO {
VC = secondVC
break
}
}
VC?.callback = { [weak self ] in
let error = ErrorView()
self?.view.addSubview(error)
}
NSLayoutConstraint.activate([
scoreLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
scoreLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
}

Add UILabel as subview of UITextField on top

I am in the process of implementing a UILabel as a subview of a UITextField which will be shown right above the UITextField itself. The UITextField has a rounded border and what I would like to achieve is the UILabel to be shown over the border.
Everything currently works as expected, but the UILabel is drawn behind the border of the UITextField. I want it to go "over" (above) the border so the white backgroundColor would be shown above part of the border and make the text more easily readible.
var priceTextField: CustomTextField = {
let priceTextField = CustomTextField()
priceTextField.layer.cornerRadius = 10.0
priceTextField.layer.borderWidth = 1.0
priceTextField.layer.borderColor = UIColor.darkGray.cgColor
priceTextField.translatesAutoresizingMaskIntoConstraints = false
priceTextField.font = UIFont.systemFont(ofSize: 15)
priceTextField.textColor = .black
priceTextField.text = "0"
priceTextField.suffix = "EUR"
priceTextField.suffixTextColor = .darkGray
priceTextField.suffixSpacing = 2.0
priceTextField.textAlignment = .center
priceTextField.labelText = "Price"
return priceTextField
}()
In my CustomTextField class (subclass of UITextField):
public var labelText: String?
var topLabel: UILabel = {
let topLabel = UILabel()
topLabel.translatesAutoresizingMaskIntoConstraints = false
topLabel.textAlignment = .center
topLabel.font = UIFont.systemFont(ofSize: 12)
topLabel.textColor = .lightGray
topLabel.backgroundColor = .white
topLabel.numberOfLines = 1
return topLabel
}()
func setupLabel() {
self.addSubview(topLabel)
topLabel.centerYAnchor.constraint(equalTo: self.topAnchor).isActive = true
topLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true
topLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
topLabel.text = labelText
}
I call setupLabel() at the end of the draw(_ rect: CGRect) method of UITextField (because I work with this to show the EUR sign always behind the entered value).
I have tried to play around with bringSubviewToFront and changing the zPosition of the layer of the UILabel, without success.
It now looks like this:
How can I bring the text "above" the border on the top?
EDIT: Tried Sh_Khan's solution, but it's still hidden behind the border.
import Foundation
import UIKit
public class CustomTextView: UIView, UITextFieldDelegate {
public var labelText: String?
var customTextField: CustomTextField = {
let customTextField = CustomTextField()
customTextField.translatesAutoresizingMaskIntoConstraints = false
customTextField.font = UIFont.systemFont(ofSize: 15)
customTextField.textColor = .black
customTextField.textAlignment = .center
customTextField.text = "0"
customTextField.suffix = "EUR"
customTextField.suffixTextColor = .lightGray
customTextField.suffixSpacing = 2.0
return customTextField
}()
var topLabel: UILabel = {
let topLabel = UILabel()
topLabel.translatesAutoresizingMaskIntoConstraints = false
topLabel.font = UIFont.systemFont(ofSize: 12)
topLabel.textColor = .darkGray
topLabel.numberOfLines = 1
topLabel.backgroundColor = .red
topLabel.textAlignment = .center
return topLabel
}()
override public init(frame: CGRect) {
super.init(frame: frame)
setupBorders()
}
public override func layoutSubviews() {
setupViews()
}
func setupBorders() {
self.layer.cornerRadius = 10.0
self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.borderWidth = 1.0
}
func setupViews() {
addSubview(topLabel)
// insertSubview(topLabel, aboveSubview: customTextField)
insertSubview(customTextField, belowSubview: topLabel)
customTextField.topAnchor.constraint(equalTo: topAnchor).isActive = true
customTextField.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
customTextField.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
customTextField.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
topLabel.centerYAnchor.constraint(equalTo: topAnchor).isActive = true
topLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
topLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
topLabel.text = labelText
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViews()
}
}
You can try to organize it by creating a UIView subclass , so everything appear properly in it's order of adding
class CustomView: UIView {
var priceTextField: CustomTextField = {
let priceTextField = CustomTextField()
priceTextField.layer.cornerRadius = 10.0
priceTextField.layer.borderWidth = 1.0
priceTextField.layer.borderColor = UIColor.darkGray.cgColor
priceTextField.translatesAutoresizingMaskIntoConstraints = false
priceTextField.font = UIFont.systemFont(ofSize: 15)
priceTextField.textColor = .black
priceTextField.text = "0"
priceTextField.suffix = "EUR"
priceTextField.suffixTextColor = .darkGray
priceTextField.suffixSpacing = 2.0
priceTextField.textAlignment = .center
priceTextField.labelText = "Price"
return priceTextField
}()
var topLabel: UILabel = {
let topLabel = UILabel()
topLabel.translatesAutoresizingMaskIntoConstraints = false
topLabel.textAlignment = .center
topLabel.font = UIFont.systemFont(ofSize: 12)
topLabel.textColor = .lightGray
topLabel.backgroundColor = .white
topLabel.numberOfLines = 1
return topLabel
}()
var lableStr:String?
init(frame: CGRect,lblTex:String) {
super.init(frame: frame)
lableStr = lblTex
createSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
createSubviews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
createSubviews()
}
func createSubviews() {
// all the layout code from above
// add the textfield then the label and set constraints properly
}
}
According to the Apple specification: It is composited above the receiver’s contents and sublayers.
So, the border will always be above all subviews, even if one brings the subview to the front and so on.
So one needs to make a background view to fake the border.
similar to Stackoverflow Question
Example:
Here self is "TextField"
activeborderView is "UiView"
activeborderView.frame = CGRect.init(x: -1, y: -1, width: self.frame.size.width+2, height: self.frame.size.height+2)
activeborderView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(activeborderView)
activeborderView.topAnchor.constraint(equalTo: self.topAnchor, constant:-1).isActive = true // Place our label 10 pts above the text field
activeborderView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: -1).isActive=true
activeborderView.heightAnchor.constraint(equalToConstant: self.frame.size.height+2).isActive=true
activeborderView.widthAnchor.constraint(equalToConstant: self.frame.size.width+2).isActive=true
activeborderView.layer.borderWidth = 3
activeborderView.layer.borderColor = CustomColor.blue().cgColor
activeborderView.layer.cornerRadius = 5
activeborderView.backgroundColor = .white
self.sendSubviewToBack(activeborderView)
self.setNeedsDisplay()

addTarget on UIButton not working

I have a UIView class
class FloatingView : UIView {
lazy var floatingButton : UIButton = {
let button = UIButton(type: UIButtonType.system)
button.setBackgroundImage(#imageLiteral(resourceName: "ic_add_circle_white_36pt"), for: .normal)
button.tintColor = UIColor().themePurple()
button.addTarget(self, action: #selector(buttonClicked), for: UIControlEvents.touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews(){ addSubview(floatingButton) }
override func didMoveToWindow() {
super.didMoveToWindow()
floatingButton.rightAnchor.constraint(equalTo: layoutMarginsGuide.rightAnchor).isActive = true
floatingButton.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor).isActive = true
floatingButton.widthAnchor.constraint(equalToConstant: 60).isActive = true
floatingButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
}
#objc func buttonClicked (){
print("Clicked..")
}
Added this to view by
let floatingButton = FloatingView()
view.addSubview(floatingButton)
I've also specified the constraints for the floating view .
The button got added to view as expected but the "buttonClicked" function not invoked when the button is clicked . The fade animation on the button when clicked is working though.I've tried UITapGesture but not working .
I've update the class as below
class FloatingView{
lazy var floatingButton : UIButton = {
let button = UIButton(type: UIButtonType.system)
button.setBackgroundImage(#imageLiteral(resourceName: "ic_add_circle_white_36pt"), for: .normal)
button.tintColor = UIColor().themePurple()
button.addTarget(self, action: #selector(buttonClicked(_:)), for: UIControlEvents.touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private var view : UIView!
func add(onview: UIView ){
view = onview
configureSubViews()
}
private func configureSubViews(){
view.addSubview(floatingButton)
floatingButton.rightAnchor.constraint(equalTo: view.layoutMarginsGuide.rightAnchor).isActive = true
floatingButton.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor).isActive = true
floatingButton.widthAnchor.constraint(equalToConstant: 60).isActive = true
floatingButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
}
#objc func buttonClicked(_ sender : UIButton){
print("Button Clicked")
}
}
And in controller
let flButton = FloatingView()
flButton.add(onview: view)
I'm trying to create a floating action button like this. I'm not sure whether I'm doing it the right way.
try to change the action to
action: #selector(buttonClicked(_:))
and the function to
#objc func buttonClicked(_ sender: UIButton){..}
I somehow fixed it by setting the constrains from within the custom class to its superview rather than from the controller.
func configureSubviews(){
if let superView = superview {
superView.addSubview(floatingButton)
widthAnchor.constraint(equalToConstant: circleSpanArea).isActive = true
heightAnchor.constraint(equalTo: widthAnchor).isActive = true
centerXAnchor.constraint(equalTo: floatingButton.centerXAnchor).isActive = true
centerYAnchor.constraint(equalTo: floatingButton.centerYAnchor).isActive = true
floatingButton.rightAnchor.constraint(equalTo: superView.layoutMarginsGuide.rightAnchor).isActive = true
floatingButton.bottomAnchor.constraint(equalTo: superView.layoutMarginsGuide.bottomAnchor).isActive = true
floatingButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
floatingButton.widthAnchor.constraint(equalToConstant: 60).isActive = true
}
}

Resources