UIAlertController Alert action TextField need to be resized-error - swift

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
}

Related

Getting trouble when hovering a custom segmented control in swift

I tried to create custom segmented control which should change its look if you are hovering it. But it does not work as I expected.
Here's the code (I am sorry that it is that long but I already made it 200 lines shorter then it was):
class MySegmentedControl: UIView {
var state = MySegmentedControl_State.unknown
var buttons = [MySegmentedControl_ButtonView]()
func setUpView(){
let view = UIView(frame: self.frame)
view.frame.origin.x = 0
view.frame.origin.y = 0
view.addSubview(holderView())
self.addSubview(view)
}
func holderView() -> UIView{
let view = UIView(frame: CGRect(origin: CGPoint(x: 100, y: 0), size: CGSize(width: 490, height: 70)))
view.layer.borderWidth = 5
view.layer.borderColor = UIColor.gray.cgColor
view.layer.cornerRadius = view.frame.height / 2
view.layer.masksToBounds = true
view.clipsToBounds = true
view.backgroundColor = #colorLiteral(red: 0.5741485357, green: 0.5741624236, blue: 0.574154973, alpha: 1)
//place the first button
let button_1 = MySegmentedControl_ButtonView(text: "One", frame: CGRect(x: 0, y: 0, width: 163.3333, height: 70), type: .one, delegate: self)
//place the second Button
let button_2 = MySegmentedControl_ButtonView(text: "Two", frame: CGRect(x: 163.3333, y: 0, width: 163.3333, height: 70), type: .two, delegate: self)
//place the third Button
let button_3 = MySegmentedControl_ButtonView(text: "Three", frame: CGRect(x: 163.3333*2, y: 0, width: 163.3333, height: 70), type: .three, delegate: self)
buttons.append(button_1); buttons.append(button_2); buttons.append(button_3)
view.addSubview(button_1)
view.addSubview(button_2)
view.addSubview(button_3)
return view
}
class MySegmentedControl_ButtonView: UIView{
private var selected = false
private var hovering = false
var text = ""
private var type: MySegmentedControl_State
private var delegate: MySegmentedControl_ButtonView_Delegate
private var label = UILabel()
func setUpView(){
layer.cornerRadius = frame.height/2
let label = UILabel(frame: frame)
self.label = label
label.tintColor = .white
label.text = text
label.sizeToFit()
label.center = self.center
label.font = label.font.withSize(15)
let regionizer = UIHoverGestureRecognizer(target: self, action: #selector(didHover(_:)))
addGestureRecognizer(regionizer)
addSubview(label)
//set up the button
let button = UIButton(frame: bounds, primaryAction: UIAction(handler: { [self] action in
selected = true
delegate.shouldChangeState(to: type)
delegate.shouldDeselectOthers(without: text)
if !hovering{
backgroundColor = #colorLiteral(red: 0.9961533629, green: 0.9931518435, blue: 1, alpha: 0.8)
}else{
self.backgroundColor = #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.9435433103)
}
}))
addSubview(button)
}
func deselect(){
self.selected = false
if hovering{
backgroundColor = #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.15)
UIView.animate(withDuration: 0.2) {[self]in
label.frame.origin.y = 10
label.font = label.font.withSize(12)
}
}else{
backgroundColor = #colorLiteral(red: 0.2605174184, green: 0.2605243921, blue: 0.260515637, alpha: 0.15)
UIView.animate(withDuration: 0.2) {[self]in
label.frame.origin.y = 10
label.font = label.font.withSize(12)
}
}
}
#objc
func didHover(_ recognizer: UIHoverGestureRecognizer){
switch recognizer.state{
case .began, .changed:
hovering = true
if selected{
backgroundColor = #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.15)
UIView.animate(withDuration: 0.2) {[self]in
label.frame.origin.y = 10
label.font = label.font.withSize(12)
}
}else{
backgroundColor = #colorLiteral(red: 0.2605174184, green: 0.2605243921, blue: 0.260515637, alpha: 0.15)
UIView.animate(withDuration: 0.2) {[self]in
label.frame.origin.y = 10
label.font = label.font.withSize(12)
}
}
case .ended:
hovering = false
if selected{
self.backgroundColor = #colorLiteral(red: 0.9961533629, green: 0.9931518435, blue: 1, alpha: 0.2)
UIView.animate(withDuration: 0.2) {[self]in
label.center.y = center.y
label.font = label.font.withSize(15)
}
}else{
self.backgroundColor = .clear
UIView.animate(withDuration: 0.2) {[self]in
label.center.y = center.y
label.font = label.font.withSize(15)
}
}
default:break
}
}
init(text: String, frame: CGRect, type: MySegmentedControl_State, delegate: MySegmentedControl_ButtonView_Delegate){
self.type = type
self.delegate = delegate
super.init(frame: frame)
self.text = text
setUpView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}
extension MySegmentedControl: MySegmentedControl_ButtonView_Delegate{
func shouldDeselectOthers(without: String) {
for button in buttons{
if button.text != without{
button.deselect()
}
}
}
func shouldChangeState(to state: MySegmentedControl_State) {
self.state = state
}
}
protocol MySegmentedControl_ButtonView_Delegate{
func shouldDeselectOthers(without: String)
func shouldChangeState(to state: MySegmentedControl_State)
}
enum MySegmentedControl_State: String{
case unknown = "Non specific case avalaible"
case one = "One selected"
case two = "Two selected"
case three = "Three selected"
}
But the second of my buttons is displayed as the third and the the third does not get displayed, but if I hover the place where button two SHOULD be, it still gets hovered.
Here is a shout video:
Video showing problems with this code
My App is running on MacOS with MacCatalyst
In setUpView() inside class MySegmentedControl_ButtonView, you are trying to set the label's position with:
label.center = self.center
However, self.center is relative to self's superview. So your labels are being shifted.
If you change that line to:
label.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
the label centering will be correct.
As I asked in my comment though... why aren't you using auto-layout? It would make things much easier (and much more flexible).

How to use "shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer"

I am trying to programmatically create 6 CGRect objects on the right side of the screen, with three being blue and three red. The user would then drag them to the left side of the screen, and they need to drag the blue blocks where the target is the faded-out region of the same color. The picture below depicts what I am trying to do.
The issue is that when I drag any block over any of the faded areas, it accepts the block and deletes it. I realize that I need to implement shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer but I am confused on how to do so. I get that gestureRecognizer and otherGestureRecognizer represent the two conflicting objects, but I don't know how to implement it with the rest of my code so that I can make sure that only blue blocks can go over the faded blue block and activate the delete feature. What I have is below.
import UIKit
class ViewController: UIViewController {
private struct Constants {
static let padding: CGFloat = 75
static let correctPadding: CGFloat = 0
static let blockDimension: CGFloat = 50
}
var targetView0: UIView?
var targetView1: UIView?
var targetView2: UIView?
var targetView3: UIView?
var targetView4: UIView?
var targetView5: UIView?
var beginningPosition: CGPoint = .zero
var initialMovableViewPosition: CGPoint = .zero
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
addMovableViews(count: 6)
addtargetView0()
addtargetView1()
addtargetView2()
addtargetView3()
addtargetView4()
addtargetView5()
}
//This programtically creates the blocks w/ x and y
private func addMovableViews(count: Int) {
var xOffset = Constants.padding
for i in 0..<count {
print(i)
let movableView = UIView(frame: CGRect(x: 700, y: xOffset + 120, width: Constants.blockDimension, height: Constants.blockDimension))
if (i == 0) {
movableView.backgroundColor = .blue
view.addSubview(movableView)
let item0 = UIPanGestureRecognizer(target: self, action: #selector(touched))
movableView.addGestureRecognizer(item0)
} else if (i == 1) {
movableView.backgroundColor = .red
view.addSubview(movableView)
let item1 = UIPanGestureRecognizer(target: self, action: #selector(touched))
movableView.addGestureRecognizer(item1)
} else if (i == 2) {
movableView.backgroundColor = .blue
view.addSubview(movableView)
let item2 = UIPanGestureRecognizer(target: self, action: #selector(touched))
movableView.addGestureRecognizer(item2)
} else if (i == 3) {
movableView.backgroundColor = .red
view.addSubview(movableView)
let item3 = UIPanGestureRecognizer(target: self, action: #selector(touched))
movableView.addGestureRecognizer(item3)
} else if (i == 4) {
movableView.backgroundColor = .blue
view.addSubview(movableView)
let item4 = UIPanGestureRecognizer(target: self, action: #selector(touched))
movableView.addGestureRecognizer(item4)
} else if (i == 5) {
movableView.backgroundColor = .red
view.addSubview(movableView)
let item5 = UIPanGestureRecognizer(target: self, action: #selector(touched))
movableView.addGestureRecognizer(item5)
} else if (i == 6) {
movableView.backgroundColor = .blue
view.addSubview(movableView)
let item6 = UIPanGestureRecognizer(target: self, action: #selector(touched))
movableView.addGestureRecognizer(item6)
}
xOffset += Constants.blockDimension + Constants.padding
}
}
private func addtargetView0() {
let xOffset = Constants.correctPadding
targetView0 = UIView(frame: CGRect(x: xOffset + 100, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
targetView0?.backgroundColor = UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)
view.addSubview(targetView0!)
}
private func addtargetView1() {
let xOffset = Constants.correctPadding
targetView1 = UIView(frame: CGRect(x: 100 + xOffset + 50, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
targetView1?.backgroundColor = UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)
view.addSubview(targetView1!)
}
private func addtargetView2() {
let xOffset = Constants.correctPadding
targetView2 = UIView(frame: CGRect(x: 100 + xOffset + 100, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
targetView2?.backgroundColor = UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)
view.addSubview(targetView2!)
}
private func addtargetView3() {
let xOffset = Constants.correctPadding
targetView3 = UIView(frame: CGRect(x: 100 + xOffset + 150, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
targetView3?.backgroundColor = UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)
view.addSubview(targetView3!)
}
private func addtargetView4() {
let xOffset = Constants.correctPadding
targetView4 = UIView(frame: CGRect(x: 100 + xOffset + 200, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
targetView4?.backgroundColor = UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)
view.addSubview(targetView4!)
}
private func addtargetView5() {
let xOffset = Constants.correctPadding
targetView5 = UIView(frame: CGRect(x: 100 + xOffset + 250, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
targetView5?.backgroundColor = UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)
view.addSubview(targetView5!)
}
//Gesture recognizer
#objc private func touched(_ gestureRecognizer: UIGestureRecognizer) {
if let touchedView = gestureRecognizer.view {
if gestureRecognizer.state == .began {
beginningPosition = gestureRecognizer.location(in: touchedView)
initialMovableViewPosition = touchedView.frame.origin
} else if gestureRecognizer.state == .ended {
//Moves it back to where it was
// touchedView.frame.origin = initialMovableViewPosition
} else if gestureRecognizer.state == .changed {
let locationInView = gestureRecognizer.location(in: touchedView)
touchedView.frame.origin = CGPoint(x: touchedView.frame.origin.x + locationInView.x - beginningPosition.x, y: touchedView.frame.origin.y + locationInView.y - beginningPosition.y)
if touchedView.frame.intersects(targetView0!.frame) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView0?.backgroundColor = .blue
initialMovableViewPosition = .zero
}
if touchedView.frame.intersects(targetView1!.frame) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView1?.backgroundColor = .red
initialMovableViewPosition = .zero
}
if touchedView.frame.intersects(targetView2!.frame) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView2?.backgroundColor = .blue
initialMovableViewPosition = .zero
}
if touchedView.frame.intersects(targetView3!.frame) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView3?.backgroundColor = .red
initialMovableViewPosition = .zero
}
if touchedView.frame.intersects(targetView4!.frame) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView4?.backgroundColor = .blue
initialMovableViewPosition = .zero
}
if touchedView.frame.intersects(targetView5!.frame) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView5?.backgroundColor = .red
initialMovableViewPosition = .zero
}
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer)
-> Bool {
// If the gesture recognizers are on diferent views, do not allow
// simultaneous recognition.
if gestureRecognizer.view != otherGestureRecognizer.view {
return false
}
return true
}
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Can someone explain how to incorporate shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer, link a complete project doing something similar, or walk me through it. Any help is appreciated.
There are a few issues here. Firstly, the delegate for your gestures are not being set. To do this, you need ViewController to extend UIGestureRecognizerDelegate like so class ViewController: UIViewController, UIGestureRecognizerDelegate. Then, you need to set the delegate of each of your gestures to your ViewController:
let item0 = UIPanGestureRecognizer(target: self, action: #selector(touched))
item0.delegate = self
movableView.addGestureRecognizer(item0)
Second, even with the delegates set, shouldRecognizeSimultaneouslyWith would never be called since you only have one type of gesture (UIPanGestureRecognizer) for each of your moveable views. Your target views themselves do not have any gesture recognisers assigned to them. If you want to test when shouldRecognizeSimultaneouslyWith is called, you can try do the following:
let item0 = UIPanGestureRecognizer(target: self, action: #selector(touched))
item0.delegate = self
movableView.addGestureRecognizer(item0)
// Add another gesture
let testitem = UITapGestureRecognizer(target: self, action: #selector(test))
movableView.addGestureRecognizer(testitem)
With the example above, both UITapGestureRecognizer and UIPanGestureRecognizer would be triggered the moment you start dragging the moveable views.
=== Solution ===
Instead of using shouldRecognizeSimultaneouslyWith might I suggest adding tags to each of your moveable and target views? For example, for all blue moveable and target views, movableView.tag = 0 and for all red moveable and target views, movableView.tag = 1.
Then, in your touched action, you can just check if the tags match like so:
if touchedView.frame.intersects(targetView0!.frame) {
if touchedView.tag == targetView0?.tag {
touchedView.removeFromSuperview()
targetView0?.backgroundColor = .blue
initialMovableViewPosition = .zero
}
}
I figured it out. It's basically just in the gesture if statement.
#objc private func touched(_ gestureRecognizer: UIGestureRecognizer) {
if let touchedView = gestureRecognizer.view {
if gestureRecognizer.state == .began {
beginningPosition = gestureRecognizer.location(in: touchedView)
initialMovableViewPosition = touchedView.frame.origin
} else if gestureRecognizer.state == .ended {
//Moves it back to where it was
// touchedView.frame.origin = initialMovableViewPosition
} else if gestureRecognizer.state == .changed {
let locationInView = gestureRecognizer.location(in: touchedView)
touchedView.frame.origin = CGPoint(x: touchedView.frame.origin.x + locationInView.x - beginningPosition.x, y: touchedView.frame.origin.y + locationInView.y - beginningPosition.y)
if ((touchedView.backgroundColor == .blue) && (targetView0?.backgroundColor == UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView0!.frame))) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView0?.backgroundColor = .blue
initialMovableViewPosition = .zero
}
if ((touchedView.backgroundColor == .red) && (targetView1?.backgroundColor == UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView1!.frame))) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView1?.backgroundColor = .red
initialMovableViewPosition = .zero
}
if ((touchedView.backgroundColor == .blue) && (targetView2?.backgroundColor == UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView2!.frame))) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView2?.backgroundColor = .blue
initialMovableViewPosition = .zero
}
if ((touchedView.backgroundColor == .red) && (targetView3?.backgroundColor == UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView3!.frame))) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView3?.backgroundColor = .red
initialMovableViewPosition = .zero
}
if ((touchedView.backgroundColor == .blue) && (targetView4?.backgroundColor == UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView4!.frame))) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView4?.backgroundColor = .blue
initialMovableViewPosition = .zero
}
if ((touchedView.backgroundColor == .red) && (targetView5?.backgroundColor == UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView5!.frame))) {
touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
// gestureRecognizer.isEnabled = false
targetView5?.backgroundColor = .red
initialMovableViewPosition = .zero
}
}
}
}
You check if the gestureRecognizer background color is blue, check if the targetView background color is the faded blue via UIColor, and see if the view intersects, and then you do what you need. You can even do it to fill the target view in the order you want with an additional if statement.

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:

Swift - Custom Text Input View

I'm trying to implement a custom text input view, similar to what is used in most messaging apps like below:
Where the whole view appears at the bottom of the screen initially then above the keyboard when selected, the text box re-sizes based on the content and includes a button to upload text.
I assume I need to create a custom UIView that contain all these elements, but am unsure how to change the textbox size and move the view above a keyboard when pressed.
Can someone point me in the right direction
Have a look at MessageInputBar
https://github.com/MessageKit/MessageInputBar
It will make your like easy and will stop you from reinventing the wheel plus its highly customisable, you can run the example to see how it is working.
Edit
Just to give you an idea
import UIKit
import MessageInputBar
class CustomInputBar: MessageInputBar {
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure() {
backgroundView.backgroundColor = UIColor(red: 245/255, green: 245/255, blue: 245/255, alpha: 1)
let button = InputBarButtonItem()
button.setSize(CGSize(width: 36, height: 36), animated: false)
button.setImage(#imageLiteral(resourceName: "ic_up").withRenderingMode(.alwaysTemplate), for: .normal)
button.imageView?.contentMode = .scaleAspectFit
button.tintColor = UIColor(red: 0, green: 122/255, blue: 1, alpha: 1)
inputTextView.backgroundColor = .white
inputTextView.placeholderTextColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1)
inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)
inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 20)
inputTextView.layer.borderColor = UIColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 1).cgColor
inputTextView.layer.borderWidth = 1.0
inputTextView.layer.cornerRadius = 4.0
inputTextView.layer.masksToBounds = true
inputTextView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
setLeftStackViewWidthConstant(to: 36, animated: false)
setStackViewItems([button], forStack: .left, animated: false)
sendButton.setSize(CGSize(width: 52, height: 36), animated: false)
}
}
which will look like this:
With all the feature you wanted plus more.
I've edited the code from the example project a little to make it look exactly as you added in the question.
And you ViewController will just be
import UIKit
import MessageInputBar
final class ExampleViewController: UITableViewController {
// MARK: - Properties
override var inputAccessoryView: UIView? {
return messageInputBar
}
override var canBecomeFirstResponder: Bool {
return true
}
// MARK: - MessageInputBar
private let messageInputBar = CustomInputBar()
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
tableView.keyboardDismissMode = .interactive
messageInputBar.delegate = self
}
}
And to listen to MessageInputBarDelegate simply add
extension ExampleViewController: MessageInputBarDelegate {
func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) {
// Use to send the message
messageInputBar.inputTextView.text = String()
messageInputBar.invalidatePlugins()
}
func messageInputBar(_ inputBar: MessageInputBar, textViewTextDidChangeTo text: String) {
// Use to send a typing indicator
}
func messageInputBar(_ inputBar: MessageInputBar, didChangeIntrinsicContentTo size: CGSize) {
// Use to change any other subview insets
}
}
Simple as that :)
If you want to do it programatically by yourself you can try this.
Custom textentry view which will containt text input and send button
import UIKit
class TextEntryView: UIView {
let tvMessage: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.textColor = Constants.charlie
textView.font = UIFont.systemFont(ofSize: 17)
textView.isScrollEnabled = false
return textView
}()
let btnSend: UIButton = {
let image: UIImage = UIImage(named: "send_icon")!
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(image, for: .normal)
button.setContentHuggingPriority(UILayoutPriority(rawValue: 250), for: .horizontal)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .white
self.addBorders(edges: .top, color: UIColor(red: 220/250, green: 220/250, blue: 220/250, alpha: 1))
setupLayout()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override class var requiresConstraintBasedLayout: Bool {
return true
}
private func setupLayout() {
self.addSubview(tvMessage)
self.addSubview(btnSend)
tvMessage.topAnchor.constraint(equalTo: self.topAnchor, constant: 6).isActive = true
tvMessage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 12).isActive = true
tvMessage.trailingAnchor.constraint(equalTo: btnSend.leadingAnchor, constant: -12).isActive = true
tvMessage.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -6).isActive = true
btnSend.topAnchor.constraint(equalTo: self.topAnchor, constant: 6).isActive = true
btnSend.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -12).isActive = true
btnSend.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -6).isActive = true
btnSend.widthAnchor.constraint(equalToConstant: 40).isActive = true
}
}
Add custom view in controller
import UIKit
class ChatController: UIViewController, UITextViewDelegate {
let textEntry = TextEntryView()
var bottomConstraint: NSLayoutConstraint?
var textEntryHeightConstraint: NSLayoutConstraint?
override func viewWillAppear(_ animated: Bool) {
initViews()
setupLayout()
NotificationCenter.default.addObserver(self,
selector: #selector(self.keyboardNotification(notification:)),
name: NSNotification.Name.UIKeyboardWillChangeFrame,
object: nil)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
tapGesture.cancelsTouchesInView = true
tableView.addGestureRecognizer(tapGesture)
}
#objc func hideKeyboard() {
self.endEditing(true)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc func keyboardNotification(notification: NSNotification) {
if let userInfo = notification.userInfo {
let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let endFrameY = keyboardFrame?.origin.y ?? 0
let duration:TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue
let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
if endFrameY >= UIScreen.main.bounds.size.height {
bottomConstraint?.constant = 0
} else {
bottomConstraint?.constant = -(keyboardFrame?.size.height)!
}
UIView.animate(withDuration: duration,
delay: TimeInterval(0),
options: animationCurve,
animations: { self.layoutIfNeeded() },
completion: nil)
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (textEntry.tvMessage.contentSize.height + 12 < (textEntryHeightConstraint?.constant)!) {
self.textEntry.tvMessage.isScrollEnabled = false
} else {
self.textEntry.tvMessage.isScrollEnabled = true
}
return true
}
func textViewDidBeginEditing(_ textView: UITextView) {
if textEntry.tvMessage.textColor == .lightGray {
textEntry.tvMessage.text = nil
textEntry.tvMessage.textColor = Constants.tertiaryColor
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if (textEntry.tvMessage.text?.isEmpty)! {
textEntry.tvMessage.text = "Write a message"
textEntry.tvMessage.textColor = .lightGray
}
}
}
extension MessageView {
func initViews() {
if #available(iOS 11.0, *) {
bottomConstraint = NSLayoutConstraint(item: textEntry, attribute: .bottom, relatedBy: .equal, toItem: self.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0)
} else {
// Fallback on earlier versions
bottomConstraint = NSLayoutConstraint(item: textEntry, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
}
textEntry.translatesAutoresizingMaskIntoConstraints = false
textEntry.tvMessage.text = "Write a message"
textEntry.tvMessage.textColor = .lightGray
textEntry.tvMessage.delegate = self
}
func setupLayout() {
self.addSubview(textEntry)
textEntry.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
textEntry.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
self.addConstraint(bottomConstraint!)
textEntryHeightConstraint = textEntry.heightAnchor.constraint(lessThanOrEqualToConstant: 150)
textEntryHeightConstraint?.isActive = true
}
}

Trouble Referencing UIButton using NSMutableAttributedString in Instantiated View Controller

As soon as my view controller loads, I am presented with a button (gray background with white font) that displays the text “Sto 1”. This is called in viewWillLayoutSubviews and the title is set using a NSMutableAttributedString. “Sto” is short for store.
For my application, I would like the user to be able to select the Sto 1 button and be able to store a number that is presented on a UILabel. I am able to grab the current number being displayed but I’m unable to update the text inside my Sto 1 button using NSMutableAttributedString. In other words I want to go from the button showing “Sto 1” to displaying some number (e.g., 12).
Thank you all for any help you may be able to provide me. I am still relatively new to Swift and I have been trying to resolve this issue over the past week.
import UIKit
class ViewController: UIViewController {
var fontConstant = CGFloat()
var someRandomNumberDisplayedOnAUILabel = String(12)
#IBOutlet weak var myButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillLayoutSubviews() {
fontConstant = 1
let myString = "Sto 1"
let myAttributes = [NSAttributedString.Key.foregroundColor : UIColor(red: 251/255.0, green: 251/255.0, blue: 251/255.0, alpha: 1.0), NSAttributedString.Key.font : UIFont.systemFont(ofSize: 10 * fontConstant)]
let mutableAttributedString = NSMutableAttributedString(string: myString, attributes: myAttributes)
myButton.setAttributedTitle(mutableAttributedString, for: .normal)
myButton.backgroundColor = UIColor(red: 94/255.0, green: 94/255.0, blue: 94/255.0, alpha: 1.0)
}
#IBAction func myButtonAction(_ sender: Any) {
let myAttributes = [NSAttributedString.Key.foregroundColor : UIColor(red: 0/255.0, green: 0/255.0, blue: 0/255.0, alpha: 1.0), NSAttributedString.Key.font : UIFont.systemFont(ofSize: 10 * fontConstant)]
let mutableAttributedString = NSMutableAttributedString(string: someRandomNumberDisplayedOnAUILabel, attributes: myAttributes)
myButton.setAttributedTitle(mutableAttributedString, for: .normal)
myButton.backgroundColor = UIColor(red: 251/255.0, green: 251/255.0, blue: 251/255.0, alpha: 1.0)
}}
Normally, you would use setTitle(:for:) to change the text on a UIButton. But since you're working with an NSMutableAttributedString you will need the setAttributedTitle(:for:) function. I think this might be what you're looking for:
myButton.setAttributedTitle(myNSMutableAttributedString, for: .normal)
Heads up, though. You might need to call this function for the different control states and not just .normal otherwise you might see different text for an instant as the button is highlighted. Here is a list of the control states.
EDIT:I would try referencing the sender in the IBAction instead of myButton. This might be a quick fix:
#IBAction func myButtonAction(_ sender: Any) {
let myAttributes = [NSAttributedString.Key.foregroundColor : UIColor(red: 0/255.0, green: 0/255.0, blue: 0/255.0, alpha: 1.0), NSAttributedString.Key.font : UIFont.systemFont(ofSize: 10 * fontConstant)]
let mutableAttributedString = NSMutableAttributedString(string: someRandomNumberDisplayedOnAUILabel, attributes: myAttributes)
guard let button = sender as? UIButton else {
print("Error: sender was not a button")
return
}
button.setAttributedTitle(mutableAttributedString, for: .normal)
button.backgroundColor = UIColor(red: 251/255.0, green: 251/255.0, blue: 251/255.0, alpha: 1.0)
}
EDIT #2:If you're losing the reference to your IBOutlet you might be able to work around that by assigning a selector to the button before you lose it. Try this:
override func viewDidLoad() {
super.viewDidLoad()
// Add the action to the button rather than holding on to the IBOutlet
myButton.addTarget(self, action: #selector(RideInProgressViewController.myAction(sender:)), for: .touchUpInside)
}
#objc private func myAction(sender: Any) {
guard let button = sender as? UIButton else {
print("Error: sender was not a button")
return
}
let myAttributes = [NSAttributedString.Key.foregroundColor : UIColor(red: 0/255.0, green: 0/255.0, blue: 0/255.0, alpha: 1.0), NSAttributedString.Key.font : UIFont.systemFont(ofSize: 10 * fontConstant)]
let mutableAttributedString = NSMutableAttributedString(string: someRandomNumberDisplayedOnAUILabel, attributes: myAttributes)
button.setAttributedTitle(mutableAttributedString, for: .normal)
button.backgroundColor = UIColor(red: 251/255.0, green: 251/255.0, blue: 251/255.0, alpha: 1.0)
}