Picker in SwiftUI 2 .onChange() does not change UINavigationBar.appearance() - swift

So this is a weird one...before SwiftUI 2 launched, I set the UINavigationBar.appearance() in the init() of the view as follows:
init(selectedStyle: Binding<Int>) {
_selectedStyle = selectedStyle
if self.selectedStyle == 1 {
UINavigationBar.appearance().backgroundColor = UIColor.init(displayP3Red: 7/255, green: 7/255, blue: 7/255, alpha: 1)
UISegmentedControl.appearance().backgroundColor = .black
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.black], for: UIControl.State.selected)
UISegmentedControl.appearance().selectedSegmentTintColor = .white
UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], for: UIControl.State.normal)
} else {
UINavigationBar.appearance().backgroundColor = .white
UISegmentedControl.appearance().backgroundColor = .white
UISegmentedControl.appearance().selectedSegmentTintColor = .white
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black]
UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.black], for: UIControl.State.selected)
UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.black], for: UIControl.State.normal)
}
}
In SwiftUI 1, the view would initialize again so the new look for the navbar would update correctly. because the init() function would run again. I tried to put the same code inside of an onChange() that's attached to the Picker but for some reason it doesn't work:
Picker(selection: $selectedStyle, label: Text("")) {
ForEach(0 ..< 3) {
Text([$0])
}
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: selectedStyle, perform: { change in
if self.selectedStyle == 1 {
UINavigationBar.appearance().backgroundColor = UIColor.init(displayP3Red: 7/255, green: 7/255, blue: 7/255, alpha: 1)
UISegmentedControl.appearance().backgroundColor = .black
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.black], for: UIControl.State.selected)
UISegmentedControl.appearance().selectedSegmentTintColor = .white
UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], for: UIControl.State.normal)
} else {
UINavigationBar.appearance().backgroundColor = .white
UISegmentedControl.appearance().backgroundColor = .white
UISegmentedControl.appearance().selectedSegmentTintColor = .white
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black]
UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.black], for: UIControl.State.selected)
UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.black], for: UIControl.State.normal)
}
})

The appearance have effect on UI elements creates after corresponding appearance is set. So you need to recreate all dependent UI.
The possible approach can be as follows - assuming you have NavigationView in root of ContentView, you can recreate it (and so all subviews), by
var body: some View {
NavigationView {
// .. content here
}.id(selectedStyle) // << here !!
}

Related

UIAlertController Alert action TextField need to be resized-error

Getting this error...
[LayoutConstraints] Changing the translatesAutoresizingMaskIntoConstraints property of a UICollectionViewCell that is managed by a UICollectionView is not supported, and will result in incorrect self-sizing. View: <_UIAlertControllerTextFieldViewCollectionCell: 0x7fe6a9f26000; frame = (0 0; 270 24); gestureRecognizers = <NSArray: 0x60000098edc0>; layer = <CALayer: 0x6000007c5cc0>>
From this...
func saveText()
{
let ac = UIAlertController(title: "NAME IT", message: nil, preferredStyle: .alert)
ac.addTextField()
let submitAction = UIAlertAction(title: "SAVE", style: .default)
{
[unowned ac] _ in
let answer = ac.textFields![0]
if answer.text!.count < 1
{
self.view.backgroundColor = .red
}
else
{
self.view.backgroundColor = .green
}
}
ac.addAction(submitAction)
present(ac, animated: true)
}
I have played around with this for a while, I have tried to figure out if I can add my own textfield to use it too...
func addTextField()
{
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
}
But no luck
I need to remove this error.
Thanks in advance
Oh, this didn't help at all
https://www.hackingwithswift.com/example-code/uikit/how-to-add-a-uitextfield-to-a-uialertcontroller
Thanks for trying to help, couldn't waste more than a day waiting for a solution so I spent two hours on a workaround instead.
I figured it would be mean of me not to share...
OR rather nice that I spent the extra 20 mins to share my workaround.
If anyone plays hero and wants to edit this, I am happy to delete the answer.
import UIKit
class ViewController: UIViewController
{
let textFieldView = UIView()
let textFieldLabel = UILabel()
let textFieldField = UITextField()
let textFieldButton = UIButton()
let WhiteUIColour: UIColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
let LightGreyUIColour: UIColor = #colorLiteral(red: 0.921431005, green: 0.9214526415, blue: 0.9214410186, alpha: 1)
let DarkBlueUIColour: UIColor = #colorLiteral(red: 0.01680417731, green: 0.1983509958, blue: 1, alpha: 1)
let MediumGreyCGColour: CGColor = #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 1)
let bold17 = UIFont.boldSystemFont(ofSize: 17)
let hel17 = UIFont (name: "Helvetica", size: 17)
let hel20 = UIFont (name: "Helvetica", size: 20)
// Create button to present the UIAlert with textField workaround view
let yourButton = UIButton()
func createYourButton()
{
view.addSubview(yourButton)
yourButton.translatesAutoresizingMaskIntoConstraints = false
yourButton.backgroundColor = .red
yourButton.setTitle("TAP ME", for: .normal)
yourButton.addTarget(self, action: #selector(saveButtonTap), for: .touchUpInside)
NSLayoutConstraint.activate([
yourButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
yourButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
yourButton.widthAnchor.constraint(equalToConstant: 100),
yourButton.heightAnchor.constraint(equalToConstant: 50),
])
}
#objc func saveButtonTap()
{
saveName()
}
func saveName()
{
textfieldUIAlert()
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200), execute:
{
self.textFieldView.isHidden = false
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300), execute:
{
self.textFieldField.becomeFirstResponder()
})
})
}
func textfieldUIAlert()
{
view.addSubview(textFieldView)
textFieldView.isHidden = true
textFieldView.translatesAutoresizingMaskIntoConstraints = false
textFieldView.backgroundColor = LightGreyUIColour
textFieldView.layer.cornerRadius = 16
textFieldView.addSubview(textFieldLabel)
textFieldLabel.translatesAutoresizingMaskIntoConstraints = false
textFieldLabel.textAlignment = .center
textFieldLabel.font = bold17
textFieldLabel.text = "NAME IT"
textFieldView.addSubview(textFieldField)
textFieldField.translatesAutoresizingMaskIntoConstraints = false
textFieldField.backgroundColor = WhiteUIColour
textFieldField.layer.borderColor = MediumGreyCGColour
textFieldField.layer.borderWidth = 0.5
textFieldField.layer .cornerRadius = 9
textFieldField.font = hel20
textFieldField.textAlignment = .center
textFieldView.addSubview(textFieldButton)
textFieldButton.translatesAutoresizingMaskIntoConstraints = false
textFieldButton.setTitleColor(DarkBlueUIColour, for: .normal)
textFieldButton.titleLabel?.font = hel17
textFieldButton.setTitle("SAVE", for: .normal)
textFieldButton.addTarget(self, action: #selector(textFieldButtonTap), for: .touchUpInside)
NSLayoutConstraint.activate([
textFieldView.topAnchor.constraint(equalTo: view.centerYAnchor, constant: -250),
textFieldView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
textFieldView.bottomAnchor.constraint(equalTo: view.centerYAnchor, constant: -90),
textFieldView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 7/10),
textFieldLabel.topAnchor.constraint(equalTo: textFieldView.topAnchor),
textFieldLabel.centerXAnchor.constraint(equalTo: textFieldView.centerXAnchor),
textFieldLabel.widthAnchor.constraint(equalTo: textFieldView.widthAnchor),
textFieldLabel.heightAnchor.constraint(equalToConstant: 50),
textFieldField.centerXAnchor.constraint(equalTo: textFieldView.centerXAnchor),
textFieldField.centerYAnchor.constraint(equalTo: textFieldView.centerYAnchor, constant: -10),
textFieldField.heightAnchor.constraint(equalToConstant: 33),
textFieldField.widthAnchor.constraint(equalTo: textFieldView.widthAnchor, multiplier: 8.5/10),
textFieldButton.topAnchor.constraint(equalTo: textFieldField.bottomAnchor, constant: 15),
textFieldButton.bottomAnchor.constraint(equalTo: textFieldView.bottomAnchor),
textFieldButton.centerXAnchor.constraint(equalTo: textFieldView.centerXAnchor),
textFieldButton.widthAnchor.constraint(equalTo: textFieldView.widthAnchor),
])
}
#objc func textFieldButtonTap()
{
if textFieldField.text!.count < 1 || textFieldField.text == " " || textFieldField.text == " " || textFieldField.text == " "
{
let TooShort = UIAlertController(title: "TOO SHORT", message: "\n\nTHE NAME\n\n YOU ARE SAVING\n\nIS TOO SHORT\n", preferredStyle: .alert)
TooShort.view.tintColor = #colorLiteral(red: 0.5818830132, green: 0.2156915367, blue: 1, alpha: 1)
TooShort.view.layer.cornerRadius = 15
TooShort.view.layer.borderWidth = 5
TooShort.view.layer.borderColor = #colorLiteral(red: 1, green: 0.1491314173, blue: 0, alpha: 1)
func okHandler(alert: UIAlertAction!)
{
// Do something if you feel like it
}
self.present(TooShort, animated: true, completion: nil)
TooShort.addAction(UIAlertAction(title: "OK", style: .default, handler: okHandler))
}
else
{
//DON'T DO WHAT I DO, DO SOMETHING EQUALLLY AWESOME
doingSomethingAwesome()
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500), execute:
{
self.textFieldField.text = .none
self.textFieldView.isHidden = true
})
}
}
func doingSomethingAwesome()
{
let saved = UIAlertController(title: "SAVED", message: "\n\nYOU HAVE\n\nSAVED THE NAME\n\n", preferredStyle: .alert)
saved.view.tintColor = #colorLiteral(red: 0.01680417731, green: 0.1983509958, blue: 1, alpha: 1)
saved.view.layer.cornerRadius = 15
saved.view.layer.borderWidth = 5
saved.view.layer.borderColor = #colorLiteral(red: 0, green: 0.9768045545, blue: 0, alpha: 1)
func okHandler(alert: UIAlertAction!)
{
...
}
self.present(saved, animated: true, completion: nil)
saved.addAction(UIAlertAction(title: "OK", style: .default, handler: okHandler))
}
override func viewDidLoad() {
super.viewDidLoad()
createYourButton()
}
}
// Actually took me 30 mins to post this, so if I've missed something, let me know✌️
// Because I have a scrollView, I figured this was next best thing to disabling it while that view was showing
func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
{
textFieldView.isHidden = true
}

how to arrange elements in UIStackView in realtime?

I have a stackView with horizontal axis and I have UIView inside it, I kinda created a graph from that ,
the red one bars are the UIViews and the gray background is the stackView(referred as mainStackView in code) , Now I want to move that bar in realtime , I am trying to make a sorting visualiser but I dont know how do I do that
In UIKit (everything is programmatic in live playground), here is the source code of the main file
Main file
if (arrayToSort[j].frame.size.height > arrayToSort[j+1].frame.size.height){
// swap
var temp:UIView!
temp = arrayToSort[j]
arrayToSort[j] = arrayToSort[j+1]
arrayToSort[j+1] = temp
emptyStackView()
fillStackView(sortdArr: arrayToSort)
// delay here
}
Here is the source code for HomeViewController
import UIKit
public class HomeViewController:UIViewController{
let stackView:UIStackView = {
let st = UIStackView()
st.axis = .horizontal
st.alignment = .center
st.distribution = .fill
st.layer.shadowColor = UIColor.gray.cgColor
st.layer.shadowOffset = .zero
st.layer.shadowRadius = 5
st.layer.shadowOpacity = 1
st.spacing = 10
st.translatesAutoresizingMaskIntoConstraints = false
return st
}()
let generateButton:UIButton = {
let btn = UIButton()
btn.setTitle("Generate Array", for: .normal)
btn.backgroundColor = UIColor(red: 0.92, green: 0.30, blue: 0.29, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let BubbleSort:UIButton = {
let btn = UIButton()
btn.setTitle("BubbleSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.41, green: 0.43, blue: 0.88, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let MergeSort:UIButton = {
let btn = UIButton()
btn.setTitle("MergeSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.10, green: 0.16, blue: 0.34, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let InsertionSort:UIButton = {
let btn = UIButton()
btn.setTitle("InsertionSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.19, green: 0.22, blue: 0.32, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let SelectionSort:UIButton = {
let btn = UIButton()
btn.setTitle("SelectionSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.51, green: 0.20, blue: 0.44, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let mainStackView:UIStackView = {
let st = UIStackView()
st.backgroundColor = .gray
st.axis = .horizontal
st.distribution = .fillEqually
st.alignment = .bottom
st.spacing = 1
st.translatesAutoresizingMaskIntoConstraints = false
return st
}()
let baseView:UIView = {
let vw = UIView()
vw.backgroundColor = UIColor(red: 0.07, green: 0.54, blue: 0.65, alpha: 1.00)
vw.translatesAutoresizingMaskIntoConstraints = false
vw.layer.cornerRadius = 3
vw.layer.masksToBounds = true
vw.heightAnchor.constraint(equalToConstant: 15).isActive = true
return vw
}()
public override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(stackView)
view.addSubview(mainStackView)
view.addSubview(baseView)
edgesForExtendedLayout = []
stackView.addArrangedSubview(generateButton)
stackView.addArrangedSubview(BubbleSort)
stackView.addArrangedSubview(MergeSort)
stackView.addArrangedSubview(InsertionSort)
stackView.addArrangedSubview(SelectionSort)
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
stackView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 50).isActive = true
baseView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant: -2).isActive = true
baseView.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 5).isActive = true
baseView.rightAnchor.constraint(equalTo: view.rightAnchor,constant: -5).isActive = true
mainStackView.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 5).isActive = true
mainStackView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 5).isActive = true
mainStackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -5).isActive = true
mainStackView.bottomAnchor.constraint(equalTo: baseView.topAnchor, constant: -5).isActive = true
setButtons()
buildRandomArray()
}
// MARK:- Actions
func setButtons(){
generateButton.addTarget(self, action: #selector(generatePressed), for: .touchUpInside)
BubbleSort.addTarget(self, action: #selector(bubbleSort), for: .touchUpInside)
MergeSort.addTarget(self, action: #selector(mergeSort), for: .touchUpInside)
InsertionSort.addTarget(self, action: #selector(insertionSort), for: .touchUpInside)
SelectionSort.addTarget(self, action: #selector(selectionSort), for: .touchUpInside)
}
func buildRandomArray(){
var randomNumber :CGFloat!
for _ in 1..<41{
let viewStick:UIView = {
let v = UIView()
v.backgroundColor = .red
v.translatesAutoresizingMaskIntoConstraints = false
randomNumber = CGFloat(Int.random(in: 160...600))
v.heightAnchor.constraint(equalToConstant: randomNumber).isActive = true
v.frame.size.height = randomNumber
return v
}()
mainStackView.addArrangedSubview(viewStick)
}
}
#objc func generatePressed(){
emptyStackView()
buildRandomArray()
print("Generating Array.....")
}
#objc func bubbleSort(){
let n = mainStackView.arrangedSubviews.count
var arrayToSort = mainStackView.arrangedSubviews
for i in 0..<n-1{
for j in 0..<n-i-1 {
if (arrayToSort[j].frame.size.height > arrayToSort[j+1].frame.size.height){
// swap
var temp:UIView!
temp = arrayToSort[j]
arrayToSort[j] = arrayToSort[j+1]
arrayToSort[j+1] = temp
self.emptyStackView()
self.fillStackView(sortdArr: arrayToSort)
// delay here
}
}
}
print("array sorted")
}
#objc func mergeSort(){
print("Merge Sort.....")
}
#objc func insertionSort(){
print("insertion Sort.....")
}
#objc func selectionSort(){
print("selection Sort.....")
}
func emptyStackView(){
for element in mainStackView.arrangedSubviews {
mainStackView.removeArrangedSubview(element)
}
}
func fillStackView(sortdArr:[UIView]){
for vw in sortdArr {
mainStackView.addArrangedSubview(vw)
}
}
}
To update the UI in a loop, you have to give UIKit a chance to run.
For example, this:
for i in 1...200 {
someView.frame.origin.x = CGFloat(i)
}
will NOT "slide the view" to the right - it will cause the view to "jump" from x: 1 to x: 200.
So, in your loop where you're trying to visualize the sorting process, nothing on the screen will update until you have completed the loop.
To have something happen each time through, you can use a Timer.
With the previous example, you could do something like this:
var i = 1
Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
someView.frame.origin.x = CGFloat(i)
i += 1
if i > 200 {
timer.invalidate()
}
}
Now the view will "slide slowly" across the screen.
So, you need to refactor your sorting code with a timer.
I made some edits to your code to let you see one approach to this task. Give it a try (it will run in a playground), and inspect the code and //comments. Changes don't start until the buildRandomArray() function:
Edit - I made a few changes to this code...
has an array of "sample values" to make it easier to re-run
added a "Reset" button to re-run with the original values
when using sample values, bars are now labels to make it clearer that the bars are being "moved"
implemented Insertion Sort
Worth noting: When running this in a playground, button taps while a sort is active are not very responsive. Works much betting with an iPad Simulator or Device.
public class SortViewController:UIViewController{
let stackView:UIStackView = {
let st = UIStackView()
st.axis = .horizontal
st.alignment = .center
st.distribution = .fillEqually
st.layer.shadowColor = UIColor.gray.cgColor
st.layer.shadowOffset = .zero
st.layer.shadowRadius = 5
st.layer.shadowOpacity = 1
st.spacing = 10
st.translatesAutoresizingMaskIntoConstraints = false
return st
}()
let generateButton:UIButton = {
let btn = UIButton()
btn.setTitle("Generate", for: .normal)
btn.backgroundColor = UIColor(red: 0.92, green: 0.30, blue: 0.29, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let resetButton:UIButton = {
let btn = UIButton()
btn.setTitle("Reset", for: .normal)
btn.backgroundColor = UIColor(red: 0.25, green: 0.75, blue: 0.29, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let BubbleSort:UIButton = {
let btn = UIButton()
btn.setTitle("Bubble", for: .normal)
btn.backgroundColor = UIColor(red: 0.41, green: 0.43, blue: 0.88, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let MergeSort:UIButton = {
let btn = UIButton()
btn.setTitle("Merge", for: .normal)
btn.backgroundColor = UIColor(red: 0.10, green: 0.16, blue: 0.34, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let InsertionSort:UIButton = {
let btn = UIButton()
btn.setTitle("Insertion", for: .normal)
btn.backgroundColor = UIColor(red: 0.19, green: 0.22, blue: 0.32, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let SelectionSort:UIButton = {
let btn = UIButton()
btn.setTitle("Selection", for: .normal)
btn.backgroundColor = UIColor(red: 0.51, green: 0.20, blue: 0.44, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let mainStackView:UIStackView = {
let st = UIStackView()
st.backgroundColor = .gray
st.axis = .horizontal
st.distribution = .fillEqually
st.alignment = .bottom
st.spacing = 1
st.translatesAutoresizingMaskIntoConstraints = false
return st
}()
let baseView:UIView = {
let vw = UIView()
vw.backgroundColor = UIColor(red: 0.07, green: 0.54, blue: 0.65, alpha: 1.00)
vw.translatesAutoresizingMaskIntoConstraints = false
vw.layer.cornerRadius = 3
vw.layer.masksToBounds = true
vw.heightAnchor.constraint(equalToConstant: 15).isActive = true
return vw
}()
public override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(stackView)
view.addSubview(mainStackView)
view.addSubview(baseView)
edgesForExtendedLayout = []
stackView.addArrangedSubview(generateButton)
stackView.addArrangedSubview(resetButton)
stackView.addArrangedSubview(BubbleSort)
stackView.addArrangedSubview(MergeSort)
stackView.addArrangedSubview(InsertionSort)
stackView.addArrangedSubview(SelectionSort)
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
stackView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 50).isActive = true
baseView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant: -2).isActive = true
baseView.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 5).isActive = true
baseView.rightAnchor.constraint(equalTo: view.rightAnchor,constant: -5).isActive = true
mainStackView.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 5).isActive = true
mainStackView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 5).isActive = true
mainStackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -5).isActive = true
mainStackView.bottomAnchor.constraint(equalTo: baseView.topAnchor, constant: -5).isActive = true
setButtons()
buildRandomArray()
}
// MARK:- Actions
func setButtons(){
generateButton.addTarget(self, action: #selector(generatePressed), for: .touchUpInside)
resetButton.addTarget(self, action: #selector(resetPressed), for: .touchUpInside)
BubbleSort.addTarget(self, action: #selector(bubbleSort), for: .touchUpInside)
MergeSort.addTarget(self, action: #selector(mergeSort), for: .touchUpInside)
InsertionSort.addTarget(self, action: #selector(insertionSort), for: .touchUpInside)
SelectionSort.addTarget(self, action: #selector(selectionSort), for: .touchUpInside)
}
let sampleValues: [Int] = [
150, 175, 200, 225, 250, 275, 300, 325, 350, 375, 400, 425, 450, 475, 500
]
var currentArray: [UIView] = []
let barColor: UIColor = .cyan
let barHighlight: UIColor = .yellow
let timerInterval: TimeInterval = 0.10
func buildRandomArray(){
// we can populate the stack view much quicker by generating an array of views
// and then looping through that array to add them as arrangedSubviews
currentArray = []
// for ease during dev, use a set of predefined values
if true {
let hh = sampleValues.shuffled()
hh.forEach { h in
let viewStick: UILabel = {
let v = UILabel()
v.backgroundColor = barColor
v.text = "\(h)"
v.textAlignment = .center
v.heightAnchor.constraint(equalToConstant: CGFloat(h)).isActive = true
return v
}()
currentArray.append(viewStick)
}
} else {
var randomNumber :CGFloat!
for _ in 1..<41{
let viewStick:UIView = {
let v = UIView()
v.backgroundColor = barColor
v.translatesAutoresizingMaskIntoConstraints = false
randomNumber = CGFloat(Int.random(in: 160...500))
v.heightAnchor.constraint(equalToConstant: randomNumber).isActive = true
return v
}()
currentArray.append(viewStick)
}
}
fillStackView(sortdArr: currentArray)
}
#objc func generatePressed(){
// stop the timer if a sort is currently running
timer?.invalidate()
print("Generating Array.....")
emptyStackView()
buildRandomArray()
}
#objc func resetPressed(){
// stop the timer if a sort is currently running
timer?.invalidate()
print("Resetting.....")
fillStackView(sortdArr: currentArray)
}
var timer: Timer?
#objc func bubbleSort(){
print("Bubble Sort....")
// if a sort is running, stop it and reset the bars
if let t = timer, t.isValid {
resetPressed()
}
var j: Int = 0
var didSwap: Bool = false
var lastBarToCheck: Int = mainStackView.arrangedSubviews.count - 1
// set current bar to first bar
var curBar = mainStackView.arrangedSubviews[0]
// set new current bar background to barHighlight
curBar.backgroundColor = barHighlight
timer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true) { timer in
// if we have more bars to check
if j < lastBarToCheck {
if self.mainStackView.arrangedSubviews[j].frame.height > self.mainStackView.arrangedSubviews[j+1].frame.height {
// next bar is shorter than current bar, so
// swap the bar positions
self.mainStackView.insertArrangedSubview(curBar, at: j + 1)
// set the didSwap flag
didSwap = true
} else {
// next bar is taller
// set current bar background back to barColor
curBar.backgroundColor = self.barColor
// set current bar to next bar
curBar = self.mainStackView.arrangedSubviews[j+1]
// set new current bar background to barHighlight
curBar.backgroundColor = self.barHighlight
}
j += 1
} else {
if !didSwap {
// no bars were swapped, so
// set current bar back to barColor
curBar.backgroundColor = self.barColor
// stop the looping
timer.invalidate()
print("Done!")
} else {
// at least one swap occurred, so
// decrement number of bars to check
lastBarToCheck -= 1
// reset index
j = 0
// set current bar back to barColor
curBar.backgroundColor = self.barColor
// set current bar background to first bar
curBar = self.mainStackView.arrangedSubviews[j]
// set new current bar background to barHighlight
curBar.backgroundColor = self.barHighlight
// reset swap flag
didSwap = false
}
}
}
}
#objc func mergeSort(){
print("Merge Sort.....")
}
#objc func insertionSort(){
print("Insertion Sort.....")
// if a sort is running, stop it and reset the bars
if let t = timer, t.isValid {
resetPressed()
}
var index: Int = 1
var position: Int = 1
// set current bar to index bar
var curBar = mainStackView.arrangedSubviews[index]
// set new current bar background to barHighlight
curBar.backgroundColor = barHighlight
timer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true) { timer in
// if we have more bars to check
if index < self.mainStackView.arrangedSubviews.count {
// if we're not at the left-most bar
if position > 0 {
// if bar-to-the-left is taller than current bar
if self.mainStackView.arrangedSubviews[position - 1].frame.height > curBar.frame.height {
// move current bar one position to the left
self.mainStackView.insertArrangedSubview(curBar, at: position - 1)
position -= 1
} else {
// bar-to-the-left is shorter than current bar
index += 1
position = index
// set current bar background back to barColor
curBar.backgroundColor = self.barColor
// if we're not finished
if index < self.mainStackView.arrangedSubviews.count {
// set current bar to next bar
curBar = self.mainStackView.arrangedSubviews[index]
// set new current bar background to barHighlight
curBar.backgroundColor = self.barHighlight
}
}
} else {
// we're at the left-most bar
// increment index
index += 1
position = index
// set current bar background back to barColor
curBar.backgroundColor = self.barColor
// if we have more bars to check
if index < self.mainStackView.arrangedSubviews.count {
// set current bar to next bar
curBar = self.mainStackView.arrangedSubviews[index]
// set new current bar background to barHighlight
curBar.backgroundColor = self.barHighlight
}
}
} else {
// we've reached the end of the array
// stop the looping
timer.invalidate()
print("Done!")
}
}
}
#objc func selectionSort(){
print("selection Sort.....")
}
func emptyStackView(){
mainStackView.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
}
func fillStackView(sortdArr:[UIView]){
sortdArr.forEach { vw in
vw.backgroundColor = self.barColor
mainStackView.addArrangedSubview(vw)
}
}
}
A tip related to stack views and their arrangedSubviews... you don't have to clear and re-populate the stack view to rearrange the views.
For example, if I add 10 labels to a stack view:
// add 10 arranged subviews
for i in 0..<10 {
let v = UILabel()
v.text = "\(i)"
v.textAlignment = .center
v.backgroundColor = .green
stackView.addArrangedSubview(v)
}
it will look like this:
If I want to swap the 4th and 5th views, I can do it like this:
// (arrays are zero-based)
stackView.insertArrangedSubview(stackView.arrangedSubviews[3], at: 4)
and I get:

Elements not adding to UIStackView , programmatically?

I was trying to make a sorting Visualizer in Live playground so I created a stack view but when I try to add UIView (as the sticks in that visualiser) they do not get added in my stackView , Below is my code
It is big but your are interested in mainStackview part only because that is the stackView where I wanna add that sticks(UIView), Also buildStartingArray() function (it is used to add the UIViews)
public class HomeViewController:UIViewController{
let stackView:UIStackView = {
let st = UIStackView()
st.axis = .horizontal
st.alignment = .center
st.distribution = .fill
// st.backgroundColor = .cyan
st.layer.shadowColor = UIColor.gray.cgColor
st.layer.shadowOffset = .zero
st.layer.shadowRadius = 5
st.layer.shadowOpacity = 1
st.spacing = 10
st.translatesAutoresizingMaskIntoConstraints = false
return st
}()
let generateButton:UIButton = {
let btn = UIButton()
btn.setTitle("Generate Array", for: .normal)
btn.backgroundColor = UIColor(red: 0.92, green: 0.30, blue: 0.29, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let BubbleSort:UIButton = {
let btn = UIButton()
btn.setTitle("BubbleSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.41, green: 0.43, blue: 0.88, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let MergeSort:UIButton = {
let btn = UIButton()
btn.setTitle("MergeSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.10, green: 0.16, blue: 0.34, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let InsertionSort:UIButton = {
let btn = UIButton()
btn.setTitle("InsertionSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.19, green: 0.22, blue: 0.32, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let SelectionSort:UIButton = {
let btn = UIButton()
btn.setTitle("SelectionSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.51, green: 0.20, blue: 0.44, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let mainStackView:UIStackView = {
let st = UIStackView()
st.backgroundColor = .gray
st.axis = .horizontal
st.distribution = .fillEqually
st.alignment = .firstBaseline
st.spacing = 1
st.translatesAutoresizingMaskIntoConstraints = false
return st
}()
let baseView:UIView = {
let vw = UIView()
vw.backgroundColor = UIColor(red: 0.07, green: 0.54, blue: 0.65, alpha: 1.00)
vw.translatesAutoresizingMaskIntoConstraints = false
vw.layer.cornerRadius = 3
vw.layer.masksToBounds = true
vw.heightAnchor.constraint(equalToConstant: 15).isActive = true
return vw
}()
public override func viewDidLoad() {
view.addSubview(mainStackView)
view.addSubview(stackView)
view.addSubview(baseView)
edgesForExtendedLayout = []
stackView.addArrangedSubview(generateButton)
stackView.addArrangedSubview(BubbleSort)
stackView.addArrangedSubview(MergeSort)
stackView.addArrangedSubview(InsertionSort)
stackView.addArrangedSubview(SelectionSort)
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
stackView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 50).isActive = true
baseView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant: -2).isActive = true
baseView.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 5).isActive = true
baseView.rightAnchor.constraint(equalTo: view.rightAnchor,constant: -5).isActive = true
mainStackView.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 5).isActive = true
mainStackView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 5).isActive = true
mainStackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -5).isActive = true
mainStackView.bottomAnchor.constraint(equalTo: baseView.topAnchor, constant: -5).isActive = true
setButtons()
builStartingArray()
}
func setButtons(){
generateButton.addTarget(self, action: #selector(generatePressed), for: .touchUpInside)
BubbleSort.addTarget(self, action: #selector(bubbleSort), for: .touchUpInside)
MergeSort.addTarget(self, action: #selector(mergeSort), for: .touchUpInside)
InsertionSort.addTarget(self, action: #selector(insertionSort), for: .touchUpInside)
SelectionSort.addTarget(self, action: #selector(selectionSort), for: .touchUpInside)
}
func builStartingArray(){
let viewStick:UIView = {
let v = UIView()
v.backgroundColor = .red
v.translatesAutoresizingMaskIntoConstraints = false
v.frame.size = CGSize(width: 50, height: 150)
return v
}()
let viewStick2:UIView = {
let v = UIView()
v.backgroundColor = .red
v.translatesAutoresizingMaskIntoConstraints = false
v.frame.size = CGSize(width: 50, height: 150)
return v
}()
mainStackView.addArrangedSubview(viewStick)
mainStackView.addArrangedSubview(viewStick2)
}
}
Problem is the UIView that I add in buildStartingArray() function doesn't show up ,And
for now I have added only 2 view but in future I am planning to add 30-35 in mainStackView,
I want those UIViews to look like graph bars and the mainStackView is my graph
Here is the current output
the mainStackView is the one with gray background
Make main stackView to align bottom
You can try to adding a height for mainStack subviews like
v.translatesAutoresizingMaskIntoConstraints = false
v.heightAnchor.constraint(equalToConstant: 150).isActive = true
in
func builStartingArray(){
let viewStick:UIView = {
let v = UIView()
v.backgroundColor = .red
v.translatesAutoresizingMaskIntoConstraints = false
v.heightAnchor.constraint(equalToConstant: 150).isActive = true
return v
}()
let viewStick2:UIView = {
let v = UIView()
v.backgroundColor = .red
v.translatesAutoresizingMaskIntoConstraints = false
v.heightAnchor.constraint(equalToConstant: 150).isActive = true
return v
}()
mainStackView.addArrangedSubview(viewStick)
mainStackView.addArrangedSubview(viewStick2)
}
All code
import UIKit
class ViewController:UIViewController{
let stackView:UIStackView = {
let st = UIStackView()
st.axis = .horizontal
st.alignment = .center
st.distribution = .fill
// st.backgroundColor = .cyan
st.layer.shadowColor = UIColor.gray.cgColor
st.layer.shadowOffset = .zero
st.layer.shadowRadius = 5
st.layer.shadowOpacity = 1
st.spacing = 10
st.translatesAutoresizingMaskIntoConstraints = false
return st
}()
let generateButton:UIButton = {
let btn = UIButton()
btn.setTitle("Generate Array", for: .normal)
btn.backgroundColor = UIColor(red: 0.92, green: 0.30, blue: 0.29, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let BubbleSort:UIButton = {
let btn = UIButton()
btn.setTitle("BubbleSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.41, green: 0.43, blue: 0.88, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let MergeSort:UIButton = {
let btn = UIButton()
btn.setTitle("MergeSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.10, green: 0.16, blue: 0.34, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let InsertionSort:UIButton = {
let btn = UIButton()
btn.setTitle("InsertionSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.19, green: 0.22, blue: 0.32, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let SelectionSort:UIButton = {
let btn = UIButton()
btn.setTitle("SelectionSort", for: .normal)
btn.backgroundColor = UIColor(red: 0.51, green: 0.20, blue: 0.44, alpha: 1.00)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = UIFont.italicSystemFont(ofSize: 20)
btn.layer.cornerRadius = 10
btn.layer.masksToBounds = true
btn.heightAnchor.constraint(equalToConstant: 38).isActive = true
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let mainStackView:UIStackView = {
let st = UIStackView()
st.backgroundColor = .gray
st.axis = .horizontal
st.distribution = .fillEqually
st.alignment = .top
st.spacing = 1
st.translatesAutoresizingMaskIntoConstraints = false
return st
}()
let baseView:UIView = {
let vw = UIView()
vw.backgroundColor = UIColor(red: 0.07, green: 0.54, blue: 0.65, alpha: 1.00)
vw.translatesAutoresizingMaskIntoConstraints = false
vw.layer.cornerRadius = 3
vw.layer.masksToBounds = true
return vw
}()
public override func viewDidLoad() {
view.addSubview(mainStackView)
view.addSubview(stackView)
view.addSubview(baseView)
edgesForExtendedLayout = []
stackView.addArrangedSubview(generateButton)
stackView.addArrangedSubview(BubbleSort)
stackView.addArrangedSubview(MergeSort)
stackView.addArrangedSubview(InsertionSort)
stackView.addArrangedSubview(SelectionSort)
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
stackView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 50).isActive = true
baseView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant: -2).isActive = true
baseView.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 5).isActive = true
baseView.rightAnchor.constraint(equalTo: view.rightAnchor,constant: -5).isActive = true
baseView.heightAnchor.constraint(equalToConstant: 15).isActive = true
mainStackView.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 5).isActive = true
mainStackView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 5).isActive = true
mainStackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -5).isActive = true
mainStackView.bottomAnchor.constraint(equalTo: baseView.topAnchor, constant: -5).isActive = true
builStartingArray()
}
func builStartingArray(){
let viewStick:UIView = {
let v = UIView()
v.backgroundColor = .red
v.translatesAutoresizingMaskIntoConstraints = false
v.heightAnchor.constraint(equalToConstant: 150).isActive = true
return v
}()
let viewStick2:UIView = {
let v = UIView()
v.backgroundColor = .red
v.translatesAutoresizingMaskIntoConstraints = false
v.heightAnchor.constraint(equalToConstant: 150).isActive = true
return v
}()
mainStackView.addArrangedSubview(viewStick)
mainStackView.addArrangedSubview(viewStick2)
}
}

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

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)

Cannot unselect the checkmark when scroll the tableView

I'm trying to do checkmark button in tableViewCell. The checkmark image will be hide/showing when user tap on the checkmark button. The problem I'm facing right now is checkmark button acting weird when I scroll the table view which is I need to double tap to unselect the checkmark.
When I do not scroll the tableView it's working great. Below are the video and code. Thanks in advance.
https://youtu.be/cQBIuIXJlRY
func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dtvc") as! DeviceTableViewCell
let scannedDevice = devices[indexPath.row]
// Set the tag & addTarget for addButton
cell.addButton.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
cell.addButton.isEnabled = true
cell.addButton.tag = indexPath.row
cell.configureCell(with: scannedDevice)
cell.isSelected = checked[indexPath.row] ?? false
print("cell isSelected at \(indexPath.row) cellForRow \(cell.isSelected)")
print("checked value at \(indexPath.row) is \(checked[indexPath.row])")
if checked[indexPath.row] == true {
cell.deviceMake.textColor = UIColor.white
cell.backgroundColor = UIColor(red: 233/255, green: 72/255, blue: 85/255, alpha: 1)
cell.addButton.setImage(UIImage(named: "Checkmark"), for: .normal)
cell.addButton.bgColor = UIColor.green
} else {
cell.deviceMake.textColor = UIColor.gray
cell.backgroundColor = UIColor.clear
cell.addButton.setImage(nil, for: .normal)
cell.addButton.bgColor = UIColor(red: 233/255, green: 72/255, blue: 85/255, alpha: 1)
}
return cell
}
#objc func addButtonTapped(_ sender: UIButton) {
let indexPath = IndexPath(row: sender.tag, section: 0)
let scannedDevice = devices[sender.tag]
let cell = tableView.cellForRow(at: indexPath) as! DeviceTableViewCell
cell.isSelected = !cell.isSelected
print("cell.isSelected is at button \(sender.tag) tapped is \(cell.isSelected)")
if cell.isSelected == true {
// Main color RGB is 233,72,85 ... hex is E94855
cell.deviceMake.textColor = UIColor.white
cell.backgroundColor = UIColor(red: 233/255, green: 72/255, blue: 85/255, alpha: 1)
cell.addButton.setImage(UIImage(named: "Checkmark"), for: .normal)
cell.addButton.bgColor = UIColor.green
checked[sender.tag] = cell.isSelected
print("Checked 1 at tag \(sender.tag) is \(checked[sender.tag])")
//show selectedDeviceView
if selectedDeviceView.isHidden {
UIView.animate(withDuration: 2.5, delay: 0.7, options: .curveEaseInOut, animations: {
self.tableViewHeightConstraint.constant = self.tableViewHeightConstraint.constant - self.selectedDeviceViewTopConstraint.constant - self.selectedDeviceView.frame.height
self.selectedDeviceViewTopConstraint.constant = 0
self.selectedDeviceView.isHidden = false
}, completion: nil)
}
}
else {
cell.deviceMake.textColor = UIColor.gray
cell.backgroundColor = UIColor.clear
cell.addButton.setImage(nil, for: .normal)
cell.addButton.bgColor = UIColor(red: 233/255, green: 72/255, blue: 85/255, alpha: 1)
checked[sender.tag] = cell.isSelected
print("Checked 2 at tag \(sender.tag) is \(checked[sender.tag])")
}
}
First of all put the selected state into the devices class/struct rather than using an extra array
class Device {
var isSelected = false
...
Second of all don't think in terms of the view think in terms of the model. That means toggle isSelected in the model and reload the row. The benefit is that the view is manipulated reliably only in cellForRow.
For example
#objc func addButtonTapped(_ sender: UIButton) {
let indexPath = IndexPath(row: sender.tag, section: 0)
devices[indexPath.row].isSelected.toggle()
self.tableView.reloadRows(at: [indexPath], with: .none)
if devices[indexPath.row].isSelected && selectedDeviceView.isHidden {
UIView.animate(withDuration: 2.5, delay: 0.7, options: .curveEaseInOut, animations: {
self.tableViewHeightConstraint.constant = self.tableViewHeightConstraint.constant - self.selectedDeviceViewTopConstraint.constant - self.selectedDeviceView.frame.height
self.selectedDeviceViewTopConstraint.constant = 0
self.selectedDeviceView.isHidden = false
}, completion: nil)
}
}
And in cellForRow change
cell.isSelected = scannedDevice.isSelected