Why don't my selector functions get called when I clicked on my radio buttons in my Xcode Swift project - swift

I have a custom UIView class called SortView with two radio buttons and neither of their respective selector functions are being called when I click on them. I have added an instance of the SortView class to a parent class with view.addView(). Here is my custom class:
class SortView: UIView {
// MARK: - Properties
lazy var driverSortRadioButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named: "Radio Button - Unselected"), for: .normal)
button.setDimensions(height: 25, width: 25)
button.backgroundColor = .clear
button.contentMode = .scaleAspectFill
button.addTarget(self, action: #selector(handleDriverSortRadioButton), for: .touchUpInside)
return button
}()
lazy var pickupTimeSortRadioButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named: "Radio Button - Unselected"), for: .normal)
button.setDimensions(height: 25, width: 25)
button.backgroundColor = .clear
button.contentMode = .scaleAspectFill
button.addTarget(self, action: #selector(handlePickupTimeSortRadioButton), for: .touchUpInside)
return button
}()
private let driverSortTitleLabel: UILabel = {
let label = UILabel()
label.text = "Sort by driver:"
label.textAlignment = .left
label.textColor = .white
label.font = UIFont(name: "AvenirNext-DemiBold", size: 18)
label.backgroundColor = .clear
return label
}()
private let pickupTimeSortTitleLabel: UILabel = {
let label = UILabel()
label.text = "Sort by pickup time:"
label.textAlignment = .left
label.textColor = .white
label.font = UIFont(name: "AvenirNext-DemiBold", size: 18)
label.backgroundColor = .clear
return label
}()
private let driverSortTextField: UITextField = {
let tf = UITextField()
tf.textColor = .black
tf.textAlignment = .center
tf.placeholder = "Louise"
tf.font = UIFont(name: "AvenirNext-DemiBold", size: 15)
tf.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
tf.setWidth(width: 150)
tf.layer.cornerRadius = 5
return tf
}()
private let pickupTimeSortTextField: UITextField = {
let tf = UITextField()
tf.textColor = .black
tf.textAlignment = .center
tf.placeholder = "2:00pm"
tf.font = UIFont(name: "AvenirNext-DemiBold", size: 15)
tf.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
tf.setWidth(width: 150)
tf.layer.cornerRadius = 5
return tf
}()
private enum radioButtonStates {
case driver
case pickupTime
}
private var radioButtonState = radioButtonStates.driver
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
configureUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Selectors
#objc func handleDriverSortRadioButton() {
print("DEBUG: driver sort radio button clicked")
if radioButtonState != .driver {
pickupTimeSortRadioButton.setImage(UIImage(named: "Radio Button - Unselected"), for: .normal)
pickupTimeSortTextField.isEnabled = false
pickupTimeSortTextField.text = ""
radioButtonState = .driver
driverSortRadioButton.setImage(UIImage(named: "Radio Button - Selected"), for: .normal)
driverSortTextField.isEnabled = true
driverSortTextField.text = ""
}
}
#objc func handlePickupTimeSortRadioButton() {
print("DEBUG: pickup time sort radio button clicked")
if radioButtonState != .pickupTime {
driverSortRadioButton.setImage(UIImage(named: "Radio Button - Unselected"), for: .normal)
driverSortTextField.isEnabled = false
driverSortTextField.text = ""
radioButtonState = .pickupTime
pickupTimeSortRadioButton.setImage(UIImage(named: "Radio Button - Selected"), for: .normal)
pickupTimeSortTextField.isEnabled = true
pickupTimeSortTextField.text = ""
}
}
// MARK: - Helper Functions
private func configureUI() {
let driverSortStackView = UIStackView(arrangedSubviews: [driverSortRadioButton,
driverSortTitleLabel,
driverSortTextField])
driverSortStackView.axis = .horizontal
driverSortStackView.distribution = .fill
driverSortStackView.spacing = 5
let pickupTimeSortStackView = UIStackView(arrangedSubviews: [pickupTimeSortRadioButton,
pickupTimeSortTitleLabel,
pickupTimeSortTextField])
pickupTimeSortStackView.axis = .horizontal
pickupTimeSortStackView.distribution = .fill
pickupTimeSortStackView.spacing = 5
let stackView = UIStackView(arrangedSubviews:[driverSortStackView,
pickupTimeSortStackView])
stackView.axis = .vertical
stackView.distribution = .fill
stackView.spacing = 10
self.addSubview(stackView)
stackView.centerX(inView: self)
stackView.centerY(inView: self)
}
}
The functions which are not being called are handleDriverSortRadioButton and handlePickupTimeSortRadioButton. Here is instantiation:
private lazy var sortView = SortView()
and here is where I use it in my parent UIViewController class:
view.addSubview(sortView)
sortView.anchor(top:tableView.bottomAnchor,
left: view.leftAnchor,
right: view.rightAnchor,
paddingTop: 40,
paddingLeft: 32,
paddingRight: 32)

I'm going to guess that the problem is that a containing view (perhaps one of the stack views) has zero size. The result would be that its subviews are visible but not tappable.
Here is a debugging utility method you can use to track down this sort of thing:
extension UIView {
#objc func reportSuperviews(filtering:Bool = true) {
var currentSuper : UIView? = self.superview
print("reporting on \(self)\n")
while let ancestor = currentSuper {
let ok = ancestor.bounds.contains(ancestor.convert(self.frame, from: self.superview))
let report = "it is \(ok ? "inside" : "OUTSIDE") \(ancestor)\n"
if !filtering || !ok { print(report) }
currentSuper = ancestor.superview
}
}
}
Wait until your interface is all set up and the buttons are untappable, and then call that on one of the untappable buttons to get a report in the console.

Fixed it by adding with and height to the bottom stack view
sortView.anchor(top:tableView.bottomAnchor,
left: view.leftAnchor,
right: view.rightAnchor,
paddingTop: 40,
paddingLeft: 32,
paddingRight: 32,
width: view.frame.width,
height: 100)

Related

iOS UIkit custom segmented buttons

I'm looking to create a view with these buttons. There is a background animation when one of the button touched.
Not sure how to do this.
Is custom segmented buttons the way to go?
I went with custom control
import UIKit
protocol MSegmentedControlDelegate:AnyObject {
func segSelectedIndexChange(to index:Int)
}
class MSegmentedControl: UIControl {
private var buttonTitles:[String]!
private var buttons: [UIButton]!
private var selectorView: UIView!
var textColor:UIColor = .black
var selectorViewColor: UIColor = .white
var selectorTextColor: UIColor = .red
weak var delegate:MSegmentedControlDelegate?
public private(set) var selectedIndex : Int = 0
convenience init(frame:CGRect,buttonTitle:[String]) {
self.init(frame: frame)
self.buttonTitles = buttonTitle
}
override func draw(_ rect: CGRect) {
super.draw(rect)
self.backgroundColor = UIColor.white
updateView()
}
func setButtonTitles(buttonTitles:[String]) {
self.buttonTitles = buttonTitles
self.updateView()
}
func setIndex(index:Int) {
buttons.forEach({ $0.setTitleColor(textColor, for: .normal) })
let button = buttons[index]
selectedIndex = index
button.setTitleColor(selectorTextColor, for: .normal)
let selectorPosition = frame.width/CGFloat(buttonTitles.count) * CGFloat(index)
UIView.animate(withDuration: 0.2) {
self.selectorView.frame.origin.x = selectorPosition
}
}
#objc func buttonAction(sender:UIButton) {
for (buttonIndex, btn) in buttons.enumerated() {
btn.setTitleColor(textColor, for: .normal)
if btn == sender {
let selectorPosition = frame.width/CGFloat(buttonTitles.count) * CGFloat(buttonIndex)
selectedIndex = buttonIndex
delegate?.segSelectedIndexChange(to: selectedIndex)
UIView.animate(withDuration: 0.3) {
self.selectorView.frame.origin.x = selectorPosition
}
btn.setTitleColor(selectorTextColor, for: .normal)
}
}
}
}
//Configuration View
extension MSegmentedControl {
private func updateView() {
createButton()
configSelectorView()
configStackView()
}
private func configStackView() {
let stack = UIStackView(arrangedSubviews: buttons)
stack.axis = .horizontal
stack.alignment = .fill
stack.distribution = .fillEqually
addSubview(stack)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
stack.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
stack.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
stack.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
}
private func configSelectorView() {
let selectorWidth = frame.width / CGFloat(self.buttonTitles.count)
selectorView = UIView(frame: CGRect(x: 0, y: 8, width: selectorWidth, height: 32))
selectorView.backgroundColor = selectorViewColor
selectorView.layer.cornerRadius = 16
selectorView.layer.opacity = 0.5
addSubview(selectorView)
}
private func createButton() {
buttons = [UIButton]()
buttons.removeAll()
subviews.forEach({$0.removeFromSuperview()})
for buttonTitle in buttonTitles {
let button = UIButton(type: .system)
button.setTitle(buttonTitle, for: .normal)
button.addTarget(self, action:#selector(MSegmentedControl.buttonAction(sender:)), for: .touchUpInside)
button.setTitleColor(textColor, for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
buttons.append(button)
}
buttons[0].setTitleColor(selectorTextColor, for: .normal)
}
}
Usage:
private let segControl: MSegmentedControl = {
let segControl = MSegmentedControl(
frame: CGRect(x: 0, y: 240, width: 280, height: 50),
buttonTitle: ["Average","Total","Pending"])
segControl.textColor = M.Colors.greyWhite
segControl.selectorTextColor = .white
return segControl
}()
To access index change event:
Implement the delegate on parent view:
addSubview(segControl)
segControl.delegate = self
Delegate:
func segSelectedIndexChange(to index: Int) {
switch index {
case 0: print("Average")
case 1: print("Total")
case 2: print("Pending")
default: break
}
}
Result:

swift tap gesture handler not invoked

I have the following custom view implementation :
import UIKit
class ProfileTableHeaderView: UITableViewHeaderFooterView {
private var statusText : String = ""
private let fullNameLabel: UILabel = {
let view = UILabel()
view.text = "Hipster Pinguin"
view.font = UIFont.systemFont(ofSize: 18, weight: .bold)
view.textColor = .black
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let avatarImage: UIImageView = {
let view = UIImageView()
view.clipsToBounds = true
view.layer.borderWidth = 3
view.layer.borderColor = UIColor.white.cgColor
view.image = UIImage(named: "avatar")
view.contentMode = .scaleAspectFill
view.layer.cornerRadius = 100/2
view.translatesAutoresizingMaskIntoConstraints = false
// let tapGesture = UITapGestureRecognizer(target : self, action : #selector(avatarImagePressHandler))
// view.isUserInteractionEnabled = true
// view.addGestureRecognizer(tapGesture)
return view
}()
let statusLabel: UILabel = {
let view = UILabel()
view.text = "Waiting for something"
view.font = UIFont.systemFont(ofSize: 14, weight: .regular)
view.textColor = .gray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let statusTextField: UITextField = {
let view = TextFieldWithPadding()
view.placeholder = "add smth to show as status"
view.layer.cornerRadius = 12
view.layer.borderWidth = 1
view.layer.borderColor = UIColor.black.cgColor
view.backgroundColor = .white
view.font = UIFont.systemFont(ofSize: 15, weight: .regular)
view.textColor = .black
view.backgroundColor = .white.withAlphaComponent(0)
view.addTarget(self, action: #selector(statusTextChanged), for : .editingChanged)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let setStatusButton: UIButton = {
let view = UIButton()
view.setTitle("Show status", for: .normal)
view.setTitleColor(.white, for : .normal)
view.backgroundColor = UIColor(named: "myColor")
view.layer.cornerRadius = 14
view.layer.shadowRadius = 4
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.7
view.layer.shadowOffset = CGSize(width: 4, height: 4)
view.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
setupViews()
}
#objc func avatarImagePressHandler()
{
print("avatar pressed")
}
#objc func buttonPressed()
{
statusLabel.text = statusText
}
#objc func statusTextChanged(_ textField: UITextField)
{
statusText = textField.text ?? ""
}
required init?(coder: NSCoder) {
fatalError("should not be called")
}
private func setupViews()
{
contentView.addSubview(avatarImage)
contentView.addSubview(fullNameLabel)
contentView.addSubview(statusLabel)
contentView.addSubview(statusTextField)
contentView.addSubview(setStatusButton)
let tapGesture = UITapGestureRecognizer(target : self, action : #selector(avatarImagePressHandler))
avatarImage.isUserInteractionEnabled = true
avatarImage.addGestureRecognizer(tapGesture)
let constraints = [
avatarImage.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16),
avatarImage.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
avatarImage.widthAnchor.constraint(equalToConstant: 100),
avatarImage.heightAnchor.constraint(equalToConstant: 100),
fullNameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 27),
fullNameLabel.leadingAnchor.constraint(equalTo: avatarImage.trailingAnchor, constant: 16),
fullNameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
statusLabel.topAnchor.constraint(equalTo: fullNameLabel.bottomAnchor, constant: 10),
statusLabel.leadingAnchor.constraint(equalTo: fullNameLabel.leadingAnchor),
statusLabel.trailingAnchor.constraint(equalTo: fullNameLabel.trailingAnchor),
statusTextField.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 10),
statusTextField.heightAnchor.constraint(equalToConstant: 40),
statusTextField.leadingAnchor.constraint(equalTo: statusLabel.leadingAnchor),
statusTextField.trailingAnchor.constraint(equalTo: statusLabel.trailingAnchor),
setStatusButton.topAnchor.constraint(equalTo: avatarImage.bottomAnchor, constant: 16),
setStatusButton.leadingAnchor.constraint(equalTo: avatarImage.leadingAnchor),
setStatusButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
setStatusButton.heightAnchor.constraint(equalToConstant: 50)
]
NSLayoutConstraint.activate(constraints)
}
}
If I try to setup a tap gesture recognizer inside lambda I see no print inside the console, but if I configure it inside setupViews everything is fine. Why does it work this way? What am I missing?

Target Action for lazy Button does not work

Using Swift 5.1.3, iOS13.3, XCode11.3,
I try to create a simple Button Target Action.
However, the Button is inside its own StackView class and moreover, is a lazy button.
Why is the Target Action not working in my Code ? i.e. callButtonMethod is never called !
Could it be because of the late API responses or is it because a lazy button cannot do target action. I am clueless at the moment.
Thank you for any help on this.
Here is my code:
class CockpitHeaderStackView: UIStackView {
weak var profileBtnDelegate: CallButtonProfileImage?
var profileImageView = UIImageView()
var profileName = "Cockpit".localized
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
if let profile = MyAPI.profile, let person = profile.person {
profileName = "\(person.firstName ?? "") \(person.lastName ?? "")"
}
MyAPI.getPicture(PictureType.avatar) { [weak self] (error, image) in
guard let self = self else { return } // check if self still alive otherwise bail out
DispatchQueue.main.async {
if let image = image {
self.profileImageView.image = image
} else {
self.profileImageView.image = #imageLiteral(resourceName: "profile-placeholder-small")
}
self.profileImageView.contentMode = .scaleAspectFill
self.axis = .horizontal
self.alignment = .bottom
self.spacing = 10.0
self.addArrangedSubview(self.titleLabel)
self.addArrangedSubview(self.button)
}
}
}
lazy var titleLabel: UILabel = {
let labelWidth: CGFloat = UIScreen.main.bounds.width - 16.0 - 10.0 - 36.0 - 16.0 // FullScreenWidth minus (Leading + Spacing + ButtonWidth + Trailing)
let label = UILabel()
label.font = AppConstants.Font.NavBar_TitleFont
label.text = profileName
label.textColor = .white
label.tintColor = .white
label.widthAnchor.constraint(equalToConstant: labelWidth).isActive = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
lazy var button: UIButton = {
let buttonWidth: CGFloat = 36.0
let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: buttonWidth, height: buttonWidth)))
button.setImage(self.profileImageView.image, for: .normal)
button.addTarget(self, action: #selector(callButtonMethod), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 36, height: 36)
button.layer.cornerRadius = button.frame.size.width / 2
button.layer.masksToBounds = false
button.clipsToBounds = true
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: buttonWidth).isActive = true
button.heightAnchor.constraint(equalToConstant: buttonWidth).isActive = true
return button
}()
#objc func callButtonMethod() {
profileBtnDelegate?.callProfileBtnMethod()
}
}
The CockpitHeaderStackView is used to create a custom NavigationBar of my ViewController.
Here the code for the custom NavigationBar with usage of the CockpitHeaderStackView :
protocol CallButtonProfileImage: AnyObject {
func callProfileBtnMethod()
}
class MyViewController: UIViewController {
// ...
lazy var titleStackView: CockpitHeaderStackView = {
let titleStackView = CockpitHeaderStackView(frame: CGRect(origin: .zero, size: CGSize(width: view.bounds.width, height: 88.0)))
titleStackView.translatesAutoresizingMaskIntoConstraints = false
return titleStackView
}()
lazy var cockpitHeaderView: UIView = {
let cockpitHeaderView = UIView(frame: CGRect(origin: .zero, size: CGSize(width: view.bounds.width, height: 88.0)))
cockpitHeaderView.addSubview(titleStackView)
titleStackView.leadingAnchor.constraint(equalTo: cockpitHeaderView.leadingAnchor, constant: 16.0).isActive = true
titleStackView.topAnchor.constraint(equalTo: cockpitHeaderView.topAnchor).isActive = true
titleStackView.trailingAnchor.constraint(equalTo: cockpitHeaderView.trailingAnchor, constant: -16.0).isActive = true
titleStackView.bottomAnchor.constraint(equalTo: cockpitHeaderView.bottomAnchor).isActive = true
return cockpitHeaderView
}()
override func viewDidLoad() {
super.viewDidLoad()
// ...
view.clipsToBounds = true
navigationController?.set_iOS12_lookAndFeel()
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationItem.largeTitleDisplayMode = .always
}
override func viewWillLayoutSubviews() {
// replace NavBar title by custom cockpitHeaderView
self.title = ""
self.navigationItem.titleView = self.cockpitHeaderView
// position the cockpitHeaderView inside the largeTitleDisplayMode NavBar
self.cockpitHeaderView.translatesAutoresizingMaskIntoConstraints = false
self.cockpitHeaderView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
if let navBarBottomAnchor = self.navigationController?.navigationBar.bottomAnchor {
if UIScreen.main.bounds.height > 568.0 {
self.cockpitHeaderView.topAnchor.constraint(equalTo: navBarBottomAnchor, constant: -48.0).isActive = true
} else {
self.cockpitHeaderView.topAnchor.constraint(equalTo: navBarBottomAnchor, constant: -46.0).isActive = true // iPhone SE space limitation
}
} else {
self.cockpitHeaderView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 89.0).isActive = true
}
}
func callProfileBtnMethod() {
print("right BarButton called here")
}
}
I finally found "a solution": The lazy initialisations seem to be the cause of the error-behaviour.
In fact, when I replace all lazy initialisation and also eliminate the StackView (called CockpitHeaderStackView) and put everything in non-lazy let-constants, then it works !!
Here is the final and relevant code (i.e. I placed everything inside the viewWillAppearmethod):
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let cardsHorizontalController = CardsHorizontalController()
self.view.addSubview(cardsHorizontalController.view)
cardsHorizontalController.view.translatesAutoresizingMaskIntoConstraints = false
cardsHorizontalController.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100.0).isActive = true
cardsHorizontalController.view.heightAnchor.constraint(equalToConstant: 279).isActive = true
cardsHorizontalController.view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
navigationController?.set_iOS12_lookAndFeel()
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationItem.largeTitleDisplayMode = .always
PeaxAPI.getPicture(PictureType.avatar) { [weak self] (error, image) in
guard let self = self else { return } // check if self still alive otherwise bail out
DispatchQueue.main.async {
if let image = image {
self.profileImageView.image = image
} else {
self.profileImageView.image = #imageLiteral(resourceName: "profile-placeholder-small")
}
self.profileImageView.contentMode = .scaleAspectFill
if let profile = PeaxAPI.profile, let person = profile.person {
self.profileName = "\(person.firstName ?? "") \(person.lastName ?? "")"
}
let titleStackView = UIStackView(frame: CGRect(origin: .zero, size: CGSize(width: self.view.bounds.width, height: 88.0)))
titleStackView.isUserInteractionEnabled = true
titleStackView.translatesAutoresizingMaskIntoConstraints = false
titleStackView.axis = .horizontal
titleStackView.alignment = .bottom
titleStackView.spacing = 10.0
let labelWidth: CGFloat = UIScreen.main.bounds.width - 16.0 - 10.0 - 36.0 - 16.0 // FullScreenWidth minus (Leading + Spacing + ButtonWidth + Trailing)
let label = UILabel()
label.font = AppConstants.Font.NavBar_TitleFont
label.text = self.profileName
label.textColor = .white
label.tintColor = .white
label.widthAnchor.constraint(equalToConstant: labelWidth).isActive = true
label.translatesAutoresizingMaskIntoConstraints = false
let buttonWidth: CGFloat = 36.0
let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: buttonWidth, height: buttonWidth)))
button.setImage(self.profileImageView.image, for: .normal)
button.isUserInteractionEnabled = true
button.addTarget(self, action: #selector(self.callProfileBtnMethod), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 36, height: 36)
button.layer.cornerRadius = button.frame.size.width / 2
button.layer.masksToBounds = false
button.clipsToBounds = true
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: buttonWidth).isActive = true
button.heightAnchor.constraint(equalToConstant: buttonWidth).isActive = true
titleStackView.addArrangedSubview(label)
titleStackView.addArrangedSubview(button)
let cockpitHeaderView = UIView(frame: CGRect(origin: .zero, size: CGSize(width: self.view.bounds.width, height: 88.0)))
cockpitHeaderView.isUserInteractionEnabled = true
cockpitHeaderView.addSubview(titleStackView)
titleStackView.leadingAnchor.constraint(equalTo: cockpitHeaderView.leadingAnchor, constant: 16.0).isActive = true
titleStackView.topAnchor.constraint(equalTo: cockpitHeaderView.topAnchor).isActive = true
titleStackView.trailingAnchor.constraint(equalTo: cockpitHeaderView.trailingAnchor, constant: -16.0).isActive = true
titleStackView.bottomAnchor.constraint(equalTo: cockpitHeaderView.bottomAnchor).isActive = true
// replace NavBar title by custom cockpitHeaderView
self.title = ""
self.navigationItem.titleView = cockpitHeaderView
cockpitHeaderView.sizeToFit()
}
}
}
I had to add addTarget after adding the button as a subview and that worked

How do I prevent one UIView from being hidden by another UIView?

I'm creating a custom, reusable segmented controller using UIViews and I'm having a problem with overlapping views. It currently looks like this:
You can see that the blue selector is under the buttons but I want it to sit at the bottom and be four pixels high. To do this, I have:
let numberOfButtons = CGFloat(buttonTitles.count)
let selectorWidth = frame.width / numberOfButtons
let selectorYPosition = frame.height - 3 <--- This cause it to be hidden behind the button
selector = UIView(frame: CGRect(x: 0, y: selectorYPosition, width: selectorWidth, height: 4))
selector.layer.cornerRadius = 0
selector.backgroundColor = selectorColor
addSubview(selector)
bringSubviewToFront(selector) <--- I thought this would work but it does nothing
which results in the selector UIView being hidden behind the segment UIView (I have the Y position set to - 3 so you can see how it's being covered up. I actually want it to be - 4, but that makes it disappear entirely):
I thought using bringSubviewToFront() would bring it in front of the segment UIView but it doesn't seem to do anything. I've looked through Apple View Programming Guide and lots of SO threads but can't find an answer.
Can anybody help me see what I'm missing?
Full code:
class CustomSegmentedControl: UIControl {
var buttons = [UIButton]()
var selector: UIView!
var selectedButtonIndex = 0
var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
var borderColor: UIColor = UIColor.black {
didSet {
layer.borderColor = borderColor.cgColor
}
}
var separatorBorderColor: UIColor = UIColor.lightGray {
didSet {
}
}
var commaSeparatedTitles: String = "" {
didSet {
updateView()
}
}
var textColor: UIColor = .lightGray {
didSet {
updateView()
}
}
var selectorColor: UIColor = .blue {
didSet {
updateView()
}
}
var selectorTextColor: UIColor = .black {
didSet {
updateView()
}
}
func updateView() {
buttons.removeAll()
subviews.forEach { $0.removeFromSuperview() }
// create buttons
let buttonTitles = commaSeparatedTitles.components(separatedBy: ",")
for buttonTitle in buttonTitles {
let button = UIButton(type: .system)
button.setTitle(buttonTitle, for: .normal)
button.setTitleColor(textColor, for: .normal)
button.backgroundColor = UIColor.white
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
buttons.append(button)
}
// make first button selected
buttons[0].setTitleColor(selectorTextColor, for: .normal)
let numberOfButtons = CGFloat(buttonTitles.count)
let selectorWidth = frame.width / numberOfButtons
let selectorYPosition = frame.height - 3
selector = UIView(frame: CGRect(x: 0, y: selectorYPosition, width: selectorWidth, height: 4))
selector.layer.cornerRadius = 0
selector.backgroundColor = selectorColor
addSubview(selector)
bringSubviewToFront(selector)
let stackView = UIStackView(arrangedSubviews: buttons)
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fillEqually
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
stackView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
stackView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
}
#objc func buttonTapped(button: UIButton) {
for (buttonIndex, btn) in buttons.enumerated() {
btn.setTitleColor(textColor, for: .normal)
if btn == button {
let numberOfButtons = CGFloat(buttons.count)
let selectorStartPosition = frame.width / numberOfButtons * CGFloat(buttonIndex)
UIView.animate(withDuration: 0.3, animations: { self.selector.frame.origin.x = selectorStartPosition })
btn.setTitleColor(selectorTextColor, for: .normal)
}
}
sendActions(for: .valueChanged)
}
}
You are covering up your selector with the stackView.
You need to do:
bringSubviewToFront(selector)
after you have added all of the views. Move that line to the bottom of updateView().

UIViewController as rootViewController of UINavigationController causes root views buttons to move

When I do this in AppDelegate:
window?.rootViewController = {
let mainController = MenuViewController()
return mainController
}()
I get this:
But when I do this in AppDelegate:
window?.rootViewController = {
let mainController = UINavigationController(rootViewController: MenuViewController())
return mainController
}()
I get this:
Why and how do I fix? Please specify which information if more information is needed.
Here is the MenuView code that lays out the buttons manually and also sets up the properties of the buttons:
class MenuView: UIView {
//title
let titleLabel: UILabel = {
let label = UILabel()
label.text = "Survive The Attackers!!"
label.backgroundColor = UIColor.white
return label
}()
//set up buttons
let newGameButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("New Game", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.backgroundColor = UIColor.white
button.layer.borderWidth = 2.0;
button.layer.borderColor = UIColor.black.cgColor
return button
}()
let resumeButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Resume Game", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.backgroundColor = UIColor.white
button.layer.borderWidth = 2.0;
button.layer.borderColor = UIColor.black.cgColor
return button
}()
let highScoresButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("High Scores", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.backgroundColor = UIColor.white
button.layer.borderWidth = 2.0;
button.layer.borderColor = UIColor.black.cgColor
return button
}()
//add subviews and initialize the view
override init(frame: CGRect){
super.init(frame: frame)
self.backgroundColor = UIColor(patternImage: UIImage(named: "background1.png")!)
addSubview(titleLabel)
addSubview(newGameButton)
addSubview(resumeButton)
addSubview(highScoresButton)
}
required init?(coder aDecoder: NSCoder) {
fatalError("It's Apple. What did you expect?")
}
//manually layout the main menu
override func layoutSubviews() {
var cursor: CGPoint = .zero
let buttonHeight = CGFloat(40.0);
let buttonWidth = CGFloat(160.0);
let labelWidth = buttonWidth + 20;
let spacing = bounds.height/4
let titleY = 2/3 * spacing
cursor.y = titleY
cursor.x = bounds.width/2 - labelWidth/2
titleLabel.frame = CGRect(x: cursor.x, y: cursor.y, width: labelWidth, height: buttonHeight)
cursor.y = spacing
cursor.x = bounds.width/2 - buttonWidth/2
newGameButton.frame = CGRect(x: cursor.x, y: cursor.y, width: buttonWidth, height: buttonHeight)
cursor.y += spacing
resumeButton.frame = CGRect(x: cursor.x, y: cursor.y, width: buttonWidth, height: buttonHeight)
cursor.y += spacing
highScoresButton.frame = CGRect(x: cursor.x, y: cursor.y, width: buttonWidth, height: buttonHeight)
}
The buttons are laid out manually in layoutSubviews
Here is my MenuView controller code:
class MenuViewController: UIViewController {
var delegateID: String = UUIDVendor.vendUUID()
private var menuView: MenuView {
return view as! MenuView
}
init(){
super.init(nibName: nil, bundle: nil)
//edgesForExtendedLayout = .init(rawValue: 0)
}
required init?(coder aDecoder: NSCoder){
fatalError()
}
//loads the view in and sizes it correctly
override func loadView() {
view = MenuView()
//extendedLayoutIncludesOpaqueBars = false
}
override func viewDidLoad() {
menuView.newGameButton.addTarget(self, action: #selector(MenuViewController.newGameButtonTapped(button:)), for: .touchUpInside)
menuView.resumeButton.addTarget(self, action: #selector(MenuViewController.resumeGameButtonTapped(button:)), for: .touchUpInside)
menuView.highScoresButton.addTarget(self, action: #selector(MenuViewController.highScoreButtonTapped(button:)), for: .touchUpInside)
menuView.setNeedsLayout()
}
//fuction that handles the event when the newGameButton is tapped
#objc func newGameButtonTapped(button: UIButton){
//reset the data in the model somehow
navigationController?.pushViewController(GameViewController(), animated: true)
}
//function that handles the event when the resume game button is tapped
#objc func resumeGameButtonTapped(button: UIButton){
}
//function that handels the event when the high scores button is tapped
#objc func highScoreButtonTapped(button: UIButton){
}
call super for layoutSubviews
private var menuView: MenuView = {
let vw = MenuView()
return vw
}()
override func viewDidLoad() {
super.viewDidLoad()
view = MenuView() //Add here
//Your code
}
And remove loadView() from MenuViewController