How to create multiple views by looping in UIKit? - ios

I want to create multiple textfields.
Instead of doing
let textfield1 = SecondTextfieldView()
let textfield2 = SecondTextfieldView()
let textfield3 = SecondTextfieldView()
let textfield4 = SecondTextfieldView()
let textfield5 = SecondTextfieldView()
let textfield6 = SecondTextfieldView()
let textfield7 = SecondTextfieldView()
let textfield8 = SecondTextfieldView()
let textfield9 = SecondTextfieldView()
let textfield10 = SecondTextfieldView()
let textfield11 = SecondTextfieldView()
let textfield12 = SecondTextfieldView()
let textfield13 = SecondTextfieldView()
let textfield14 = SecondTextfieldView()
let textfield15 = SecondTextfieldView()
let textfield16 = SecondTextfieldView()
let textfield17 = SecondTextfieldView()
let textfield18 = SecondTextfieldView()
let textfield19 = SecondTextfieldView()
let textfield20 = SecondTextfieldView()
I want to loop it -- make it short and sweet. How can I do it in UIkit (Swift)?
***** NOT IN SWIFTUI *****

try this code:
create stack view for adding all multiple views
let stackView = UIStackView.init()
stackView.spacing = 2
stackView.distribution = .fillProportionally
stackView.axis = .vertical
self.view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 20).isActive = true
stackView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 20).isActive = true
stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20).isActive = true
stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20).isActive = true
create loop for create subView like this:
for i in 0 ... 9 { // create 10 UIView
let view = UIView.init()
view.backgroundColor = colors[i] // sample color for see diffrence
// insert to stackView or etc for showing
stackView.addArrangedSubview(view)
}
create array color for assign to subView:
var colors: [UIColor] = [.red, .black ,.blue,.green, .yellow, .brown, .red, .blue, .yellow, .brown]
output:

Here's some sample code I've used in a previous project:
private enum Feature: String {
case intro
case account
var selector: Selector {
switch self {
case .intro: return #selector(navigateToIntro)
case .account: return #selector(navigateToAccount)
}
}
}
override func loadView() {
let views = createButtons(for: .intro, .account)
view = createStackView(with: views)
}
private func createButtons(for features: Feature...) -> [UIView] {
features.map {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 10))
button.setTitleColor(.systemBlue, for: .normal)
button.setTitle($0.rawValue.capitalized, for: .normal)
button.accessibilityIdentifier = $0.rawValue
button.addTarget(self, action: $0.selector, for: .touchUpInside)
return button
}
}
private func createStackView(with subViews: [UIView]) -> UIStackView {
let stackView = UIStackView(arrangedSubviews: subViews)
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.alignment = .fill
stackView.spacing = 10
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}
It's designed so you can put items in the enum at the top and iterate through them all in loadView(), but you can set them up using viewDidLoad() too.

Related

Swift - Subviews frame.maxY reading incorrectly

I have a basic sign up screen set up programmatically with the UI elements inside a view that is itself inside a scroll view.
The last UI element in the screen is a register button. I set up a keyboard notification observer with the Will Show and Will Hide notifications.
I am running this code on iPod touch 7th gen simulator.
My problem is when trying to read the maxY value of the sign up button and compare it to the keyboard minY it prints wrong numbers.
The keyboard is clearly blocking the register button which mean the button's maxY value will be greater the the keyboard minY value.
However the values printed shows that there is something wrong with the reading of the register button frame.
Here is my code:
import UIKit
class RegisterVC: UIViewController {
private let scrollView: UIScrollView = {
let scroll = UIScrollView()
scroll.clipsToBounds = true
scroll.isScrollEnabled = true
scroll.translatesAutoresizingMaskIntoConstraints = false
scroll.showsVerticalScrollIndicator = false
return scroll
}()
private let scrollInnerView: UIView = {
let innerView = UIView()
innerView.translatesAutoresizingMaskIntoConstraints = false
return innerView
}()
private let profilePic: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "person.circle")
imageView.contentMode = .scaleAspectFit
imageView.tintColor = .gray
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
private let usernameField: UITextField = {
let field = UITextField()
field.autocapitalizationType = .none
field.autocorrectionType = .no
field.returnKeyType = .next
field.layer.cornerRadius = 12
field.layer.borderWidth = 1
field.layer.borderColor = UIColor.lightGray.cgColor
field.placeholder = "Username..."
field.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: 0))
field.leftViewMode = .always
field.backgroundColor = .white
field.keyboardType = .default
field.isHighlighted = false
field.textAlignment = .left
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
private let emailField: UITextField = {
let field = UITextField()
field.autocapitalizationType = .none
field.autocorrectionType = .no
field.returnKeyType = .next
field.layer.cornerRadius = 12
field.layer.borderWidth = 1
field.layer.borderColor = UIColor.lightGray.cgColor
field.placeholder = "Email Address..."
field.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: 0))
field.leftViewMode = .always
field.backgroundColor = .white
field.keyboardType = .default
field.textAlignment = .left
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
private let passwordField: UITextField = {
let field = UITextField()
field.autocapitalizationType = .none
field.autocorrectionType = .no
field.returnKeyType = .done
field.layer.cornerRadius = 12
field.layer.borderWidth = 1
field.layer.borderColor = UIColor.lightGray.cgColor
field.placeholder = "Password..."
field.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: 0))
field.leftViewMode = .always
field.backgroundColor = .white
field.isSecureTextEntry = true
field.textAlignment = .left
field.keyboardType = .default
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
private let registerButton: UIButton = {
let button = UIButton()
button.setTitle("Create Account", for: .normal)
button.backgroundColor = .systemGreen
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 12
button.layer.masksToBounds = true
button.titleLabel?.font = .systemFont(ofSize: 20, weight: .bold)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
title = "Create Account"
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(scrollInnerView)
scrollInnerView.addSubview(profilePic)
scrollInnerView.addSubview(usernameField)
scrollInnerView.addSubview(emailField)
scrollInnerView.addSubview(passwordField)
scrollInnerView.addSubview(registerButton)
usernameField.delegate = self
emailField.delegate = self
passwordField.delegate = self
profilePic.isUserInteractionEnabled = true
registerButton.addTarget(self,
action: #selector(registerButtonTapped),
for: .touchUpInside)
setUpKeyboard()
setUpConstraints()
}
private func setUpConstraints() {
// Scroll View Constraints
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// Scroll Inner View Constraints
scrollInnerView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
scrollInnerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollInnerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
scrollInnerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
scrollInnerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
scrollInnerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor, constant: 1).isActive = true
// Profile Picture Constraints
profilePic.widthAnchor.constraint(equalTo: scrollInnerView.widthAnchor, multiplier: 1/3).isActive = true
profilePic.heightAnchor.constraint(equalTo: scrollInnerView.widthAnchor, multiplier: 1/3).isActive = true
profilePic.centerXAnchor.constraint(equalTo: scrollInnerView.centerXAnchor).isActive = true
profilePic.topAnchor.constraint(equalTo: scrollInnerView.topAnchor, constant: 10).isActive = true
// User Name Field Constraints
usernameField.widthAnchor.constraint(equalTo: scrollInnerView.widthAnchor, constant: -60).isActive = true
usernameField.heightAnchor.constraint(equalToConstant: 45).isActive = true
usernameField.topAnchor.constraint(equalTo: profilePic.bottomAnchor, constant: 10).isActive = true
usernameField.centerXAnchor.constraint(equalTo: profilePic.centerXAnchor).isActive = true
// Email Field Constraints
emailField.widthAnchor.constraint(equalTo: usernameField.widthAnchor).isActive = true
emailField.heightAnchor.constraint(equalTo: usernameField.heightAnchor).isActive = true
emailField.topAnchor.constraint(equalTo: usernameField.bottomAnchor, constant: 10).isActive = true
emailField.centerXAnchor.constraint(equalTo: usernameField.centerXAnchor).isActive = true
// Password Field Constraints
passwordField.widthAnchor.constraint(equalTo: emailField.widthAnchor).isActive = true
passwordField.heightAnchor.constraint(equalTo: emailField.heightAnchor).isActive = true
passwordField.topAnchor.constraint(equalTo: emailField.bottomAnchor, constant: 10).isActive = true
passwordField.centerXAnchor.constraint(equalTo: emailField.centerXAnchor).isActive = true
// Register Button Constraints
registerButton.widthAnchor.constraint(equalTo: passwordField.widthAnchor).isActive = true
registerButton.heightAnchor.constraint(equalTo: passwordField.heightAnchor).isActive = true
registerButton.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 20).isActive = true
registerButton.centerXAnchor.constraint(equalTo: passwordField.centerXAnchor).isActive = true
}
private func setUpKeyboard() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowNotification), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideNotification), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc private func keyboardWillShowNotification(_ notification: NSNotification) {
guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
else {
return
}
print(keyboardSize.minY)
print(registerButton.frame.maxY)
}
}
It's because the keyboard frame and the button frame are in two different coordinate systems. You cannot compare them directly. You need to convert the button frame to window coordinates before comparing them. Or else convert the keyboard frame to the button frame coordinates (the button's superview).
Actually what I typically do is convert the keyboard frame to the internal coordinates of the target view and compare that to the target view's bounds. For example:
// n is the notification
let d = n.userInfo!
var r = d[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect
r = self.slidingView.convert(r, from:nil) // <- this is the key move!
let h = self.slidingView.bounds.intersection(r).height
That tells me whether the keyboard would cover the sliding view, and if so, by how much.

Swift: Programmatic Autolayout broken by UNavigationController

I have inherited a UIKit app where my predecessor has written all the interface code by hand. I have a view controller, a simple login screen that works just fine, but when I add it to a UINavigationController it is oddly stretched. This doesn't make a lot of sense to me.
I feel like I must be missing some simple flag, or what do I need to do to make the Navcontroller play nice with this programmatic autolayout (which is hopefully my last ever)
// this a view controller extension
func apply(constraints: [NSLayoutConstraint]) {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(constraints)
}
There's my handy dandy, seemingly sane utility for applying constraints. As an example, the nice little oval thing making my password textfield looks nice has these constraints set up.
// these constraints are being setup in the view controller
emailCapsuleView.apply(constraints: [
emailCapsuleView.topAnchor.constraint(equalTo: subBigTitle.bottomAnchor, constant: 24.0),
emailCapsuleView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
emailCapsuleView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
emailCapsuleView.heightAnchor.constraint(equalToConstant: Dimensions.inputFieldHeight)
])
Outside of a UINavigationController everything is fine,
but in a UINavigationController everythhing is super wide and broken (simulator shot)
The width is not the problem here - if I use a constant for the width, my subview is still off center despite having it's centerX sets to the view controller's view's center X.
Configure your navigation controller and after that try like this:
set your objects:
let emailTextfield: UITextField = {
let tf = UITextField()
tf.backgroundColor = .white
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor.lightGray.cgColor
tf.setPadding(left: 10, right: 10)// use my extension below
tf.attributedPlaceholder = NSAttributedString(string: "Email address", attributes: [.foregroundColor: UIColor.lightGray])
tf.layer.cornerRadius = 14
tf.clipsToBounds = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let passTextfield: UITextField = {
let tf = UITextField()
tf.backgroundColor = .white
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor.lightGray.cgColor
tf.setPadding(left: 10, right: 10)// use my extension below
tf.attributedPlaceholder = NSAttributedString(string: "Password", attributes: [.foregroundColor: UIColor.lightGray])
tf.layer.cornerRadius = 14
tf.clipsToBounds = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let loginButton: UIButton = {
let b = UIButton(type: .system)
b.backgroundColor = .black
b.setTitle("Save Image", for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
b.setTitleColor(.white, for: .normal)
b.layer.cornerRadius = 14
b.clipsToBounds = true
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
let subBigTitle: UILabel = {
let l = UILabel()
l.text = "Your Big Title"
l.font = .systemFont(ofSize: 30, weight: .regular)
l.textColor = .black
l.textAlignment = .center
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
Now in viewDidLoad set stackView, bigTitle label and constraints:
view.addSubview(subBigTitle)
subBigTitle.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
subBigTitle.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
subBigTitle.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
subBigTitle.heightAnchor.constraint(equalToConstant: 60).isActive = true
let stackView = UIStackView(arrangedSubviews: [emailTextfield, passTextfield, loginButton])
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 10
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 170).isActive = true
stackView.topAnchor.constraint(equalTo: subBigTitle.bottomAnchor, constant: 24).isActive = true
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
to add text padding to your textfield use my extension:
extension UITextField {
func setPadding(left: CGFloat, right: CGFloat){
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: left, height: self.frame.size.height))
self.leftView = paddingView
self.leftViewMode = .always
let paddingViewRight = UIView(frame: CGRect(x: 0, y: 0, width: right, height: self.frame.size.height))
self.rightView = paddingViewRight
self.rightViewMode = .always
}
}
and this is the result:

UIScrollView overlaps other UIElements so they are not clickable anymore

Dear StackOverflowCommunity,
im currently working on a project, where I need to make a fully dynamic user interface.
For that, im building all of it programatically to make it as easy as possible.
I've now come to a problem which is as soon as I wrap my contentView(UIStackView) with an UIScrollView to make it scrollable, the scrollView is in front of all the other UIElements so that I can only scroll. I can not interact with Buttons, Sliders, Switches or anything, no event will get triggered.
I've literally done anything I could think of (working on that Problem for DAYS) but couldn't find any suiting answer whether on google directly nor on stack overflow or apple forums.
I'm pretty sure its a pretty small change that I just wasn't capable of thinking of. I really appreciate any of your help.
Structure is like this:
ViewController > UIScrollView > UIStackView > Item Wrappers (for example contains a UISwitch and a Describing Label) > Single UIElement
On User Interaction (for example choosing a different mode) the wrappers get removed and/or added to the view as the user needs.
I did only post the code that is somehow relevant for this problem (in my opinion). If you need any further information feel free to ask.
I maybe need to add:
As soon as I remove the UIScrollView and just add the StackView (named contentView in code) to the main view it all works just fine, I just can't scroll it which is a big problem as soon as I have more than like 5 element wrappers attached to the view.
var wrappers : Dictionary<String, UIView> = [:]
var elements : Dictionary<String, [UIView]> = [:]
var constraints : Dictionary = [String: [[NSLayoutConstraint]]]()
let contentView = UIStackView()
let states = [
"state_operating_hours",
"state_dim",
"state_brightness_sensor",
"state_operating_voltage",
"state_circuit_voltage",
"state_load_current_led",
"state_output_power",
"state_temperature",
"state_error",
"state_sw_version",
"state_hw_version"
]
let checkboxes = [
"summertime_wintertime",
"dali",
"error_output"
]
let sliders = [
"immediate_sensitivity",
"immediate_dawn",
"immediate_dim",
"immediate_day",
"immediate_dusk",
"immediate_night",
"aging"
]
let textInputs = [
"module_name",
"switch_delay"
]
let dropdowns = [
"mode",
"bt_state",
"config_output"
]
let timePickers = [
"phase_0",
"phase_1",
"phase_2",
"phase_3"
]
let buttons = [
"state_trigger",
"reset_trigger",
]
let l_kind_criteria = [
"immediate_dawn",
"immediate_day",
"immediate_dusk",
"immediate_night",
"immediate_sensitivity"
]
let d_kind_criteria = [
"immediate_dim"
]
let t_kind_criteria = [
"phase_0",
"phase_1",
"phase_2",
"phase_3"
]
let m_kind_criteria = [
"immediate_dawn",
"immediate_day",
"immediate_dusk",
"immediate_night",
"immediate_sensitivity",
"phase_0",
"phase_1"
]
let user_criteria = [
//"access",
//"state_trigger",
//"reset_trigger",
"mode",
"summertime_wintertime"
]
let service_criteria = [
"module_name",
//"access",
"state_trigger",
"reset_trigger",
"mode",
"bt_state",
"config_output",
"aging",
"switch_delay",
"summertime_wintertime",
"error_output",
"dali"
]
override func viewDidLoad() {
bleService.delegate = self
bleService.requestAuthMode()
view.backgroundColor = .lightGray
bleService.send(aText: "c28r:#")
bleService.send(aText: "c05r:#")
Toast.show(message: "Statuswerte werden abgerufen..." , controller: self)
buildLayout()
}
// Class - Functions
func buildLayout() {
// Building the Basic Layout
let topView = UIView()
topView.backgroundColor = .purple
self.view.addSubview(topView)
topView.translatesAutoresizingMaskIntoConstraints = false
let logoImageView = UIImageView(image: UIImage(named: "placeholder"))
logoImageView.translatesAutoresizingMaskIntoConstraints = false
logoImageView.frame = CGRect(x: 0, y: 0, width: view.frame.width/1.8, height: 30)
topView.addSubview(logoImageView)
logoImageView.leftAnchor.constraint(greaterThanOrEqualTo: view.leftAnchor, constant: 20).isActive = true
logoImageView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor, constant: 30).isActive = true
topView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
topView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
topView.heightAnchor.constraint(equalToConstant: view.frame.height/3).isActive = true
topView.centerYAnchor.constraint(equalTo: view.topAnchor).isActive = true
topView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
//Generate and add Scroll View to Main Window
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: 20),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
//Add Content Stack to Scroll View
contentView.axis = .vertical
contentView.alignment = .fill
contentView.spacing = 150
contentView.distribution = .fill
contentView.backgroundColor = .blue
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentView)
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 20),
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -20),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.widthAnchor.constraint(equalToConstant: scrollView.frame.width),
contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
// programmatically creating layout elements without constraints
// Elements that change a value are always last in their respective array
for (index, dropdownName) in dropdowns.enumerated() {
constraints[dropdownName] = [[]]
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = dropdownName
let leadAnch = label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[dropdownName]!.append([leadAnch])
let textField = UITextField()
textField.delegate = self
textField.translatesAutoresizingMaskIntoConstraints = false
textField.backgroundColor = .white
textField.layer.cornerRadius = 5
var trailAnch = textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
var widthAnch = textField.widthAnchor.constraint(equalToConstant: view.frame.width / 6)
constraints[dropdownName]!.append([trailAnch, widthAnch])
let pickerView = UIPickerView()
pickerView.backgroundColor = .white
pickerView.translatesAutoresizingMaskIntoConstraints = false
pickerView.delegate = self
pickerView.isHidden = true
pickerView.dataSource = self
trailAnch = pickerView.trailingAnchor.constraint(equalTo: textField.trailingAnchor)
widthAnch = pickerView.widthAnchor.constraint(equalTo: textField.widthAnchor)
constraints[dropdownName]!.append([trailAnch, widthAnch])
let dropdownWrapper = UIView()
dropdownWrapper.translatesAutoresizingMaskIntoConstraints = false
dropdownWrapper.addSubview(label)
dropdownWrapper.addSubview(textField)
dropdownWrapper.addSubview(pickerView)
wrappers[dropdownName] = dropdownWrapper
elements[dropdownName] = [label, textField, pickerView]
let commandID = bleService.getCommand(commandName: dropdownName)
bleService.send(aText: "c\(commandID)r:#")
}
for (index, sliderName) in sliders.enumerated() {
constraints[sliderName] = [[]]
let descLabel = UILabel()
descLabel.translatesAutoresizingMaskIntoConstraints = false
descLabel.text = sliderName
var leadAnch = descLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[sliderName]!.append([leadAnch])
let valueLabel = UILabel()
valueLabel.translatesAutoresizingMaskIntoConstraints = false
valueLabel.text = "0"
valueLabel.backgroundColor = .white
let widthAnch = valueLabel.widthAnchor.constraint(equalToConstant: view.frame.width/6)
var trailAnch = valueLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
constraints[sliderName]!.append([trailAnch, widthAnch])
let slider = UISlider()
slider.translatesAutoresizingMaskIntoConstraints = false
slider.isContinuous = false
slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
leadAnch = slider.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
trailAnch = slider.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
let topAnch = slider.topAnchor.constraint(equalTo: descLabel.bottomAnchor, constant: 5)
constraints[sliderName]!.append([trailAnch, leadAnch, topAnch])
let sliderWrapper = UIView()
sliderWrapper.translatesAutoresizingMaskIntoConstraints = false
sliderWrapper.addSubview(descLabel)
sliderWrapper.addSubview(valueLabel)
sliderWrapper.addSubview(slider)
wrappers[sliderName] = sliderWrapper
elements[sliderName] = [descLabel, valueLabel, slider]
let commandID = bleService.getCommand(commandName: sliderName)
bleService.send(aText: "c\(commandID)r:#")
}
for (index, checkboxName) in checkboxes.enumerated() {
constraints[checkboxName] = [[]]
let cbLabel = UILabel()
cbLabel.translatesAutoresizingMaskIntoConstraints = false
cbLabel.text = checkboxName
let leadAnch = cbLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[checkboxName]!.append([leadAnch])
let checkbox = UISwitch()
checkbox.translatesAutoresizingMaskIntoConstraints = false
checkbox.addTarget(self, action: #selector(checkboxClicked), for: .valueChanged)
let trailAnch = checkbox.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
constraints[checkboxName]!.append([trailAnch])
let checkboxWrapper = UIView()
checkboxWrapper.translatesAutoresizingMaskIntoConstraints = false
checkboxWrapper.addSubview(cbLabel)
checkboxWrapper.addSubview(checkbox)
wrappers[checkboxName] = checkboxWrapper
elements[checkboxName] = [cbLabel, checkbox]
let commandID = bleService.getCommand(commandName: checkboxName)
bleService.send(aText: "c\(commandID)r:#")
}
for (index, textInputName) in textInputs.enumerated() {
constraints[textInputName] = [[]]
let textLabel = UILabel()
textLabel.translatesAutoresizingMaskIntoConstraints = false
textLabel.text = textInputName
var leadAnch = textLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[textInputName]!.append([leadAnch])
let inputField = UITextField()
inputField.layer.cornerRadius = 5
inputField.translatesAutoresizingMaskIntoConstraints = false
inputField.placeholder = textInputs[index]
inputField.backgroundColor = .white
inputField.addTarget(self, action: #selector(textfieldChanged), for: .valueChanged)
let topAnch = inputField.topAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 5)
let widthAnch = inputField.widthAnchor.constraint(equalToConstant: view.safeAreaLayoutGuide.layoutFrame.width/1.1)
leadAnch = inputField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[textInputName]!.append([topAnch, widthAnch, leadAnch])
let inputWrapper = UIView()
inputWrapper.translatesAutoresizingMaskIntoConstraints = false
inputWrapper.addSubview(textLabel)
inputWrapper.addSubview(inputField)
wrappers[textInputName] = inputWrapper
elements[textInputName] = [textLabel, inputField]
let commandID = bleService.getCommand(commandName: textInputName)
bleService.send(aText: "c\(commandID)r:#")
}
for(index, phase) in timePickers.enumerated() {
constraints[phase] = [[]]
let descLabel = UILabel()
descLabel.translatesAutoresizingMaskIntoConstraints = false
descLabel.text = "Zeitschaltung \(index+1)"
var leadAnch = descLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
constraints[phase]!.append([leadAnch])
let enabledSwitch = UISwitch()
enabledSwitch.translatesAutoresizingMaskIntoConstraints = false
enabledSwitch.addTarget(self, action: #selector(changeTimerState), for: .valueChanged)
var topAnch = enabledSwitch.topAnchor.constraint(equalTo: descLabel.topAnchor)
var trailAnch = enabledSwitch.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
constraints[phase]!.append([trailAnch, topAnch])
let showPickerButton = UIButton()
showPickerButton.translatesAutoresizingMaskIntoConstraints = false
showPickerButton.setTitle("Zeit auswählen", for: .normal)
showPickerButton.backgroundColor = .darkGray
showPickerButton.layer.cornerRadius = 5
showPickerButton.addTarget(self, action: #selector(showTimePicker), for: .touchUpInside)
topAnch = showPickerButton.topAnchor.constraint(equalTo: enabledSwitch.bottomAnchor, constant: 4)
trailAnch = showPickerButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
constraints[phase]!.append([topAnch, trailAnch])
let timePicker = UIDatePicker()
timePicker.backgroundColor = .white
timePicker.isHidden = true
timePicker.translatesAutoresizingMaskIntoConstraints = false
timePicker.datePickerMode = .time
timePicker.addTarget(self, action: #selector(changeTimer), for: .valueChanged)
topAnch = timePicker.bottomAnchor.constraint(equalTo: enabledSwitch.bottomAnchor)
trailAnch = timePicker.trailingAnchor.constraint(equalTo: enabledSwitch.trailingAnchor)
constraints[phase]!.append([topAnch, trailAnch])
//Brightness Slider Value Label
let sliderValLabel = UILabel()
sliderValLabel.translatesAutoresizingMaskIntoConstraints = false
sliderValLabel.text = "0"
sliderValLabel.backgroundColor = .white
trailAnch = sliderValLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
topAnch = sliderValLabel.topAnchor.constraint(equalTo: showPickerButton.bottomAnchor, constant: 10)
var widthAnch = sliderValLabel.widthAnchor.constraint(equalToConstant: view.frame.width / 6)
constraints[phase]!.append([trailAnch, topAnch, widthAnch])
//Brightness Slider
let valueSlider = UISlider()
valueSlider.isContinuous = false
valueSlider.translatesAutoresizingMaskIntoConstraints = false
topAnch = valueSlider.topAnchor.constraint(equalTo: sliderValLabel.bottomAnchor, constant: 10)
leadAnch = valueSlider.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
trailAnch = valueSlider.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
constraints[phase]!.append([topAnch, leadAnch, trailAnch])
let timePickerWrapper = UIView()
//timePickerWrapper.translatesAutoresizingMaskIntoConstraints = false
timePickerWrapper.addSubview(descLabel)
timePickerWrapper.addSubview(enabledSwitch)
timePickerWrapper.addSubview(showPickerButton)
timePickerWrapper.addSubview(timePicker)
timePickerWrapper.addSubview(valueSlider)
timePickerWrapper.addSubview(sliderValLabel)
wrappers[phase] = timePickerWrapper
elements[phase] = [descLabel, showPickerButton, enabledSwitch, timePicker, sliderValLabel, valueSlider]
let commandID = bleService.getCommand(commandName: phase)
bleService.send(aText: "c\(commandID)r:#")
}
for buttonName in buttons {
constraints[buttonName] = [[]]
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
let widthAnch = button.widthAnchor.constraint(equalToConstant: contentView.frame.width/1.1)
let xAnch = button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
constraints[buttonName]!.append([widthAnch, xAnch])
let buttonWrapper = UIView()
buttonWrapper.translatesAutoresizingMaskIntoConstraints = false
wrappers[buttonName] = buttonWrapper
elements[buttonName] = [button]
}
}
func changeContent(criteria: [String]) {
for item in criteria {
if(!contentView.contains(wrappers[item]!)) {
contentView.addArrangedSubview(wrappers[item]!)
for singleView in constraints[item]! {
for singleViewConstraint in singleView {
singleViewConstraint.isActive = true
}
}
}
}
}
func removeContent() {
var criteria = [String]()
switch(previousSetupMode) {
case "d":
criteria = d_kind_criteria
break
case "l":
criteria = l_kind_criteria
break
case "m":
criteria = m_kind_criteria
break
case "t":
criteria = t_kind_criteria
break
default:
break
}
for item in criteria {
wrappers[item]!.removeFromSuperview()
}
}
func changeView() {
if(previousSetupMode != activeSetupMode) {
removeContent()
}
switch(activeSetupMode) {
case "d":
changeContent(criteria: d_kind_criteria)
break
case "l":
changeContent(criteria: l_kind_criteria)
break
case "t":
changeContent(criteria: t_kind_criteria)
break
case "m":
changeContent(criteria: m_kind_criteria)
break
default:
break
}
}
The problem is that you are not giving your "wrapper" views any height, so the controls are being placed outside the bounds of their parent views.
You can confirm this two ways...
1) In your
for (index, sliderName) in sliders.enumerated() {
block, add:
sliderWrapper.backgroundColor = .green
(after creating the sliderWrapper view, of course). When you run the app, you won't see the green background, because sliderWrapper has a height of Zero:
2) And / or add:
sliderWrapper.clipsToBounds = true
and you won't see the controls at all:
To solve this, you can add constraints:
let sliderWrapper = UIView()
sliderWrapper.translatesAutoresizingMaskIntoConstraints = false
sliderWrapper.backgroundColor = .green
sliderWrapper.addSubview(descLabel)
sliderWrapper.addSubview(valueLabel)
sliderWrapper.addSubview(slider)
// add a topAnchor constraint from the top of descLabel to the top of sliderWrapper
// center valueLabel vertically to descLabel
// and a bottomAnchor from the bottom of slider to the bottom of sliderWrapper (negative if you want "padding")
NSLayoutConstraint.activate([
descLabel.topAnchor.constraint(equalTo: sliderWrapper.topAnchor, constant: 8.0),
valueLabel.centerYAnchor.constraint(equalTo: descLabel.centerYAnchor),
slider.bottomAnchor.constraint(equalTo: sliderWrapper.bottomAnchor, constant: -8.0),
])
Now, background is visible... controls are visible... and controls can be interacted with:

UIButton in a view with animation not detecting touch

I'm following a tutorial to create an interactive popup animation (http://www.swiftkickmobile.com/building-better-app-animations-swift-uiviewpropertyanimator/), and now would like to add buttons to the popup rather than have static text as in the tutorial.
The animation works fine, but the buttons are not detecting touch. Touching the button causes the animation to reverse, instead of printing my "test" statement.
From research, it looks to either be an issue with view hierarchies, the animation disabling the button touch, or layout/constraint issues with the button. I've tried addressing the above issues, but nothing has worked, was hoping someone might be able to help?
I've left out the code pertaining to the animation since I think the issue is to do with layout (and the animation seems to be working fine) - but it's still a lot; apologies in advance for the large amount of code.
class ViewController: UIViewController {
private let popupOffset: CGFloat = 440
private lazy var contentImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "Background")
return imageView
}()
private lazy var overlayView: UIView = {
let view = UIView()
view.backgroundColor = .black
view.alpha = 0
return view
}()
private lazy var popupView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.1
view.layer.shadowRadius = 10
return view
}()
private lazy var closedTitleLabel: UILabel = {
let label = UILabel()
label.text = "Hello"
label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.medium)
label.textColor = UIColor.darkGray
label.textAlignment = .center
return label
}()
private lazy var openTitleLabel: UILabel = {
let label = UILabel()
label.text = "Which door will you choose?"
label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.medium)
label.textColor = UIColor.darkGray
label.textAlignment = .center
label.alpha = 0
label.transform = CGAffineTransform(scaleX: 1.6, y: 1.6).concatenating(CGAffineTransform(translationX: 0, y: 15))
return label
}()
private lazy var reviewsImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "LabelBackground")
return imageView
}()
let stackView = UIStackView()
let buttonA = UIButton()
let buttonB = UIButton()
let buttonC = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
layout()
popupView.addGestureRecognizer(panRecognizer)
}
override var prefersStatusBarHidden: Bool {
return true
}
//Layout
private var bottomConstraint = NSLayoutConstraint()
private func layout() {
contentImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(contentImageView)
contentImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
contentImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
contentImageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
contentImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
overlayView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(overlayView)
overlayView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
overlayView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
overlayView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
overlayView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
popupView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(popupView)
popupView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
popupView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
bottomConstraint = popupView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: popupOffset)
bottomConstraint.isActive = true
popupView.heightAnchor.constraint(equalToConstant: 500).isActive = true
closedTitleLabel.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(closedTitleLabel)
closedTitleLabel.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
closedTitleLabel.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
closedTitleLabel.topAnchor.constraint(equalTo: popupView.topAnchor, constant: 20).isActive = true
openTitleLabel.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(openTitleLabel)
openTitleLabel.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
openTitleLabel.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
openTitleLabel.topAnchor.constraint(equalTo: popupView.topAnchor, constant: 20).isActive = true
reviewsImageView.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(reviewsImageView)
reviewsImageView.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
reviewsImageView.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
reviewsImageView.bottomAnchor.constraint(equalTo: popupView.bottomAnchor).isActive = true
reviewsImageView.heightAnchor.constraint(equalToConstant: 428).isActive = true
buttonA.backgroundColor = UIColor.clear
let heightConstraintA = buttonA.heightAnchor.constraint(equalToConstant: 135)
heightConstraintA.isActive = true
heightConstraintA.priority = UILayoutPriority(rawValue: 999)
buttonA.translatesAutoresizingMaskIntoConstraints = false
buttonA.setTitle("A", for: .normal)
buttonA.setTitleColor(UIColor.darkGray, for: .normal)
buttonA.backgroundColor = UIColor.clear
buttonA.addTarget(self, action: #selector(buttonATapped(sender:)), for: .touchDown)
//self.popupView.addSubview(buttonA)
buttonB.backgroundColor = UIColor.clear
let heightConstraintB = buttonB.heightAnchor.constraint(equalToConstant: 135)
heightConstraintB.isActive = true
heightConstraintB.priority = UILayoutPriority(rawValue: 999)
buttonB.translatesAutoresizingMaskIntoConstraints = false
buttonB.setTitle("B", for: .normal)
buttonB.setTitleColor(UIColor.darkGray, for: .normal)
buttonB.backgroundColor = UIColor.clear
//self.popupView.addSubview(buttonB)
buttonC.backgroundColor = UIColor.clear
let heightConstraintC = buttonC.heightAnchor.constraint(equalToConstant: 135)
heightConstraintC.isActive = true
heightConstraintC.priority = UILayoutPriority(rawValue: 999)
buttonC.translatesAutoresizingMaskIntoConstraints = false
buttonC.setTitle("C", for: .normal)
buttonC.setTitleColor(UIColor.darkGray, for: .normal)
buttonC.backgroundColor = UIColor.clear
//self.popupView.addSubview(buttonC)
popupView.addSubview(stackView)
stackView.backgroundColor = UIColor.clear
stackView.addArrangedSubview(buttonA)
stackView.addArrangedSubview(buttonB)
stackView.addArrangedSubview(buttonC)
stackView.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(stackView)
stackView.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: popupView.bottomAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 428).isActive = true
stackView.axis = .vertical
stackView.distribution = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
}
#objc func buttonATapped(sender: UIButton) {
print ("test")
}
private func animateTransitionIfNeeded(to state: State, duration: TimeInterval) {
//Animation code
}
#objc private func popupViewPanned(recognizer: UIPanGestureRecognizer) {
//Animation code
}
}
***For anyone else having the same issue, here is how I solved it, thanks to #OverD:
I removed the reviewsImageView completely (because that was only for color in my case, and I can easily add the color to the UIButton instead) Then instead of adding the buttons to the popupView, I added them to the stack view, and the stack view to the popupView. Lastly, my syntax for addTarget was not correct, and for some reason changing it to touchDown instead of touchUpInside made it work.
I noticed from the provided code that you are adding the same buttons multiple times, once as a subview of the popView and once in the stackView. Also you are not assigning any targets for the buttons.
I hope this helps

Setting up UI constraint (Image and stackView inside a UIView) programmatically in Swift

I'm trying to build a custom AD when the app opens it pop up some UIViews and image and two buttons then control it from my Firebase, for now I have problem adding the adImage and buttonsStack(contains 2 buttons) inside my backView programmatically and so far nothing works ..
I need the image takes ~ %75 of the backView up and the buttonsStack ~ %25 of the rest
here some code and I have upload it to my GitHub
import UIKit
class ViewController: UIViewController {
let backroundView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .black
view.alpha = 0.5
return view
}()
let backView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.layer.cornerRadius = 15
return view
}()
let adImage: UIImageView = {
var image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.contentMode = .scaleAspectFill
return image
}()
let buttonsStack: UIStackView = {
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.alignment = UIStackViewAlignment.fill
stack.axis = UILayoutConstraintAxis.vertical
stack.distribution = .equalSpacing
stack.spacing = 8
stack.backgroundColor = UIColor.red
return stack
}()
let actionButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Open", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = UIColor(red: 0, green: 0.60, blue: 1, alpha: 1)
button.layer.cornerRadius = 8
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .center
return button
}()
let dismessButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Exit", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .lightGray
button.layer.cornerRadius = 8
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.textAlignment = .center
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
func setupUI(){
// backroundView
view.addSubview(backroundView)
backroundView.frame = view.frame
// backView
view.addSubview(backView)
backView.topAnchor.constraint(equalTo: view.topAnchor, constant: 80).isActive = true
backView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true
backView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -25).isActive = true
backView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 25).isActive = true
// adImage
backView.addSubview(adImage)
adImage.image = UIImage(named: "testImage")
adImage.topAnchor.constraint(equalTo: backView.topAnchor).isActive = true
adImage.rightAnchor.constraint(equalTo: backView.rightAnchor).isActive = true
adImage.leftAnchor.constraint(equalTo: backView.leftAnchor).isActive = true
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.50).isActive = true
// buttonsStack
buttonsStack.addArrangedSubview(actionButton)
buttonsStack.addArrangedSubview(dismessButton)
backView.addSubview(buttonsStack)
buttonsStack.topAnchor.constraint(equalTo: backView.topAnchor, constant: 15).isActive = true
buttonsStack.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -15).isActive = true
buttonsStack.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: -15).isActive = true
buttonsStack.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 15).isActive = true
}
}
For the image to take 0.75 change this
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.50).isActive = true
to
adImage.heightAnchor.constraint(equalTo: backView.heightAnchor, multiplier: 0.75).isActive = true
//
then the buttonStack should goes under it so change this
buttonsStack.topAnchor.constraint(equalTo: backView.topAnchor, constant: 15).isActive = true
to
buttonsStack.topAnchor.constraint(equalTo: adImage.bottomAnchor, constant: 15).isActive = true

Resources