I want to set a UISwitch within a UINavigationBar. But when I try place my finger on the switch and drag it to "switch" on and off the view is not responding.
This is what i have.
https://github.com/rchampa/views-within-navigationItem
As already stated in the comments above your GitHub project does not contain any data. Nevertheless everything works as expected (and seems cleaner to me) if you set the custom UIBarButtonItem up programmatically:
override func viewDidLoad() {
super.viewDidLoad()
setupBarButtonItem()
}
private func setupBarButtonItem() {
let offLabel = UILabel()
offLabel.font = UIFont.boldSystemFont(ofSize: UIFont.smallSystemFontSize)
offLabel.text = "OFF"
let onLabel = UILabel()
onLabel.font = UIFont.boldSystemFont(ofSize: UIFont.smallSystemFontSize)
onLabel.text = "ON"
let toggle = UISwitch()
toggle.addTarget(self, action: #selector(toggleValueChanged(_:)), for: .valueChanged)
let stackView = UIStackView(arrangedSubviews: [offLabel, toggle, onLabel])
stackView.spacing = 8
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: stackView)
}
#objc func toggleValueChanged(_ toggle: UISwitch) {
print("new value: \(toggle.isOn)")
}
Update:
I made it work via storyboard too. In contrast to setting it up programmatically you have to embed the UIStackView into a regular UIView to be able to add it as a UIBarButtonItem in storyboard. Then I added top, leading, bottom and trailing constraints (each with a constant of 0) from the UIStackView to its superview. To get rid of the storyboard warnings and errors at design time (at runtime it works without any problems) you have to manually calculate and set the width for the outer view (which contains the UIStackView) that is needed to enclose all of it subviews (offLabel.width + spacing + toggle.width + spacing + onLabel.width).
Related
I'm newbie and I've wrote a customView inherited from StackView and I created a button programmatically with few attributes and when I add it to my custom view, I have two problems:
If I use addArrangedSubview(myBtn), my view ignores attributes that
I added and fills the whole width. But if I use addSubView(myBtn),
It's ok(a blue square in 44x44)
If I use addArrangedSubview(myBtn), addTarget() not works and
myBtn is not clickable, but when I use addSubView(myBtn), It works
perfectly.
Here is my custom view class:
import UIKit
class RatingControl: UIStackView {
//MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setupButtons()
}
required init(coder: NSCoder) {
super.init(coder:coder)
setupButtons()
}
//MARK: Private Methods
private func setupButtons() {
// Create the button
let button = UIButton()
button.backgroundColor = UIColor.blue
// Add constraints
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
// Setup the button action
button.addTarget(self, action: #selector(ratingButtonTapped(_:)), for: .touchUpInside)
// Add the button to the stack
addArrangedSubview(button)
}
//MARK: Button Action
#objc func ratingButtonTapped(_ sender: Any) {
print("Button pressed 👍")
}
}
Here is the preview:
What's difference between addSubView() and addArrangedSubview()? why these problems happens?
I'm assuming you want to add several buttons horizontally (such as with a typical "stars" rating control).
A UIStackView, as might be guessed from its .addArrangedSubview() method, arranges its subviews, based on its .axis, .alignment, .distribution and .spacing properties, as well as its frame.
So, do some reading about UIStackView and how it can be used. Most likely, you are currently constraining your custom view with:
top
leading
trailing (or width)
So adding your button as an arrangedSubView results in it stretching to the width of the stack view, because that's the default.
Adding it as a subview simply overlays the button on the stack view, instead of allowing the stack view to arrange it, and your stack view likely then has a height of zero -- so the button cannot be tapped.
Try setting only top and leading constraints when you add your custom stack view. That should give you a 44 x 44 button that can be tapped.
As you add more buttons using .addArrangedSubview(), those buttons will be arranged horizontally, which is probably what you want.
I've 3 buttons inside a horizontal stack view. I want to shift buttons to left if one or two buttons are hidden. It's basically left shift operation. I've tried few options with stack view in storyboard but not sure if I'm on right track.
How to do it in stack view or otherwise?
Update the stackview trailing constraint while hide the button.
#IBOutlet weak var stackViewTrailing: NSLayoutConstraint!
func hideButton(button: UIButton) -> Void {
button.isHidden = true
stackViewTrailing.constant += button.frame.width
}
If you have a stack view with NO constraints set, the size of the stack view is that of its contents. Lets say the brackets [] were your stack view and X represents your buttons, if you give the stack view only a leading constraint, a vertical constraint of any kind and set the distribution to "Fill Equally" it will behave as follows:
---8px---[X X X X]
remove/hide one button:
---8px---[X X X]
This sounds like the behaviour you are seeking.
Another note: If the buttons are not distributed equally by your stack view even though you have its distribution set to "Fill Equally", make sure to give your first button (or more) a width and height constraint.
Very Simple. Follow the below steps.
1) First provide the proper Constrain for the StackView after the setup your all 3 images into it.
2) Then give a fixed width to the stack view.And create the StackView Constrain Width Outlet. Check the image.
#IBOutlet var discardWidth: NSLayoutConstraint!
3) Count the StackView width with the 2 images. In my case the width of stackView with 3 images is 100px and with 2 images it is 69px.
4) Coding as follow.
if // **Your Condition** {
img1.isHidden = true
discardWidth.constant = 69
} else {
img1.isHidden = false
discardWidth.constant = 100
Simple Right. Its just show you proper image without the stretching image. Check below image.
Take an IBOutlet Connection to your buttons. Now put some conditions on your buttons. And when these conditions are met, you can set the alignment of your button to left. Try this programmatically.
// These are IBOUtlet Collections
#IBOutlet var buttons: [UIButton]!
#IBOutlet var hideButtons: [UIButton]!
override func viewDidLoad() {
super.viewDidLoad()
configureButtons()
}
private func configureButtons() {
for (index, button) in buttons.enumerated() {
button.tag = index
button.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)
}
for (index, button) in hideButtons.enumerated() {
button.tag = index
button.addTarget(self, action: #selector(hidePressed(_:)), for: .touchUpInside)
button.setTitle("Show", for: .selected)
}
}
#objc private func hidePressed(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
buttons[sender.tag].isHidden = sender.isSelected
var totalHiddenCount = 0
for button in buttons {
if button.isHidden == true {
totalHiddenCount += 1
}
}
for button in buttons {
if totalHiddenCount >= 2 {
button.contentHorizontalAlignment = .left
} else {
button.contentHorizontalAlignment = .center
}
}
}
#objc private func buttonPressed(_ sender: UIButton) {}
As the title says, I have a custom UITableCell in which I have some UIStackViews. Each of those stacks contains many subviews but I just want to show three of them when the cell is displayed for the first time. If a user wants to see more, there is a [+] button that calls a method that adds the remaining.
The custom cell height is determined via UITableViewAutomaticDimension and it works perfectly for the first display of the cell but when I try to add and remove subviews to the stack, there are views that shouldn't be modified that lose they constraints and the ones that should be displayed doesn't do it in some cases. What I'd like is to show all the UILabels and the height of the cell to be updated.
The method that is called when the button [+] is pressed is the following:
#objc private func changeImage(sender: UIButton) {
let index = (Int(sender.accessibilityValue!)!)
let open : Bool = openItem[index]
let plateStack : UIStackView = plateStacks[index]
let plates : [UILabel] = platesViews[index]
if !open {
sender.setImage(UIImage(named: "less")?.withRenderingMode(.alwaysTemplate), for: .normal)
let nPlatesToAdd = max(platesViews[index].count - 3, 0)
for i in 0..<nPlatesToAdd {
let plate = plates[i + 3]
plateStack.addArrangedSubview(plate)
plate.leadingAnchor.constraint(equalTo: plateStack.leadingAnchor, constant: 0).isActive = true
plate.trailingAnchor.constraint(equalTo: plateStack.trailingAnchor, constant: 0).isActive = true
}
}
else {
sender.setImage(UIImage(named: "more")?.withRenderingMode(.alwaysTemplate), for: .normal)
var i = plateStack.arrangedSubviews.count - 1
while i > 2 {
let view = plateStack.arrangedSubviews[i]
plateStack.removeArrangedSubview(view)
view.removeFromSuperview()
i = i - 1
}
}
openItem[index] = !open
}
The first display of the cell (everything's ok) and after click on the [+] button:
It happened because tableView is already rendered its layout.
You might need to check some causes :
make sure the stackView constraint is properly put to contentView
stackView's distribution must be fill
After you change something that affects tableView height, you can use these code to update cell height without reloading the table:
tableView.beginUpdates()
tableView.endUpdates()
I have a view with three textfield/textview fields in it, and in order to make the view scrollable when the keyboard appears, I have put all the elements inside a scrollview. The elements where not outside the frame before, so the contentSize should be the same size as the full screen size and then when the keyboard appears, so when the keyboard appears i update the bottom constraints for the scrollview to be -keyboardHeight from the view bottom and then it is scrollable above the keyboard..
This all works fine, the problem is adding a button to the bottom of the scrollview that leading/trailing to the sides and bottom of the view.
See pictures:
Current view, with button hidden behind navigation bar
Where i would like the button to be. Approx 20 margin to right.left.bottom
I am using SnapKit to set my constraints, and for the button I would like to do something like this:
sharebutton.snp.makeConstraints { (make) in
make.width.left.right.equalToSuperview()
make.bottom.equalToSuperview().offset(-20)
}
(The Superview/the scrollview is already set as 20 margin from sides)
//EDIT: Code added ->
override func viewDidLayoutSubviews() {
scrollview.contentSize = CGSize(width: centerView.frame.width, height: view.frame.height)
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "title"
view.backgroundColor = .white
view.addSubview(scrollview)
scrollview.addSubview(sharebutton)
scrollview.addSubview(subjectField)
scrollview.addSubview(messageField)
scrollview.addSubview(datepicker)
scrollview.addSubview(addressTable)
addressTable.dataSource = self
addressTable.delegate = self
addressTable.separatorStyle = .none
addressTable.keyboardDismissMode = .onDrag
addressTable.separatorStyle = .singleLineEtched
subjectField.placeholder = "content"
datepicker.setTitle("topbutton", for: .normal)
datepicker.setTitleColor(.black, for: .normal)
datepicker.setTitleColor(UIColor.ME.border, for: .highlighted)
datepicker.titleLabel?.font = UIFont.ME.button
datepicker.layer.borderWidth = 1
datepicker.layer.cornerRadius = 3
datepicker.layer.borderColor = UIColor.ME.border.cgColor
datepicker.addTarget(self, action: #selector(showDates), for: .touchUpInside)
sharebutton.setTitle("Share", for: .normal)
sharebutton.backgroundColor = UIColor.ME.buttonMainBlue
sharebutton.addTarget(self, action: #selector(shareClicked), for: .touchUpInside)
scrollview.backgroundColor = .red
scrollview.snp.makeConstraints { (make) in
make.top.bottom.equalToSuperview()
make.left.width.right.equalTo(centerView)
}
datepicker.snp.makeConstraints { (make) in
make.top.equalToSuperview().offset(paddingGlobal)
make.left.width.right.equalToSuperview()
make.height.equalTo(50)
}
addressTable.snp.makeConstraints { (make) in
make.top.equalTo(datepicker.snp.bottom)
make.left.right.equalTo(datepicker)
make.height.equalTo(200)
}
subjectField.snp.makeConstraints { (make) in
make.top.equalTo(datepicker.snp.bottom).offset(20)
make.left.right.equalToSuperview()
}
messageField.snp.makeConstraints { (make) in
make.top.equalTo(subjectField.snp.bottom).offset(20)
make.height.equalTo(200)
make.left.right.equalToSuperview()
}
sharebutton.snp.makeConstraints { (make) in
make.width.left.right.equalToSuperview()
make.bottom.equalToSuperview().offset(-20)
}
}
Any thoughts ?
You need to add a UIView (for ex name it containerView) inside a scrollView subview first then add all your uielement(eg. textfield/textview, UIButton) to the subView of that containerView.
as Govind commented, I could solve it by placing my subviews in a container view.
Using StoryBoard and Constaint Xcode 11.5 iOS 13
StoryBoard View Controller Scene
I want 2 labels (say leftLabel, rightLabel) and place them horizontally such that leftLabel stretches and rightLabel just fits single character icon (say, ">"). Thus both labels layout justified. Like this...
This is the code I have -
class StackViewController: UIViewController {
/// Main vertical outer/container stack view that pins its edges to this view in storyboard (i.e. full screen)
#IBOutlet weak private var containerStackView: UIStackView!
private var leftLabel: UILabel = {
let leftLabel = UILabel(frame: .zero)
leftLabel.font = .preferredFont(forTextStyle: .body)
leftLabel.numberOfLines = 0 // no text truncation, allows wrap
leftLabel.backgroundColor = .orange
return leftLabel
}()
private var rightLabel: UILabel = {
let rightLabel = UILabel(frame: .zero)
rightLabel.font = .preferredFont(forTextStyle: .body)
// Set CHCR as high so that label sizes itself to fit the text
rightLabel.setContentHuggingPriority(UILayoutPriorityDefaultHigh, for: .horizontal)
rightLabel.setContentCompressionResistancePriority(UILayoutPriorityDefaultHigh, for: .horizontal)
rightLabel.backgroundColor = .green
return rightLabel
}()
override func viewDidLoad() {
super.viewDidLoad()
prepareAndLoadSubViews()
// Note, the text required to be set in viewDidAppear, not viewDidLoad, otherwise rightLabel stretches to fill!!
leftLabel.text = "This is left label text that may go in multiple lines"
rightLabel.text = ">" // Always a single character
}
/// Dynamically creates a horizontal stack view, with 2 labels, in the container stack view
private func prepareAndLoadSubViews() {
/// Prepare the horizontal label stack view and add the 2 labels
let labelStackView = UIStackView(arrangedSubviews: [leftLabel, rightLabel])
labelStackView.axis = .horizontal
labelStackView.distribution = .fillProportionally
labelStackView.alignment = .top
containerStackView.addArrangedSubview(labelStackView)
containerStackView.addArrangedSubview(UIView())
}
}
Which gives below result (i.e. leftLabel width is 0 in view debugger) -
NOTE: If I move text set code in viewDidAppear then it works fine.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Note, the text required to be set in viewDidAppear, not viewDidLoad, otherwise rightLabel stretches to fill!!
leftLabel.text = "This is left label text that may go in multiple lines"
rightLabel.text = ">" // Always a single character
}
Why?
And, can we set content hugging/ compression resistance priorities before viewDidLoad?
I played around with your code quite a bit but I was not able to make it work either. I think this is a bug that occurs when you add a UIStackView to another UIStackView. When you only have one UIStackView your code works fine.
So I cannot offer a fix for your case but IMHO you shouldn't really need to use a UIStackView for your 2 labels at all. UIStackView is great if you have multiple arranged subviews that you hide and show and need to be arranged automatically. For just two "static" labels I think it is a bit of an overkill.
You can achieve what you are after by adding your two labels to a UIView and then set layout constraints to the labels. It's really easy:
class StackViewController: UIViewController {
#IBOutlet weak var containerStackView: UIStackView!
private var leftLabel: UILabel = {
let leftLabel = UILabel(frame: .zero)
leftLabel.font = .preferredFont(forTextStyle: .body)
leftLabel.numberOfLines = 0
leftLabel.backgroundColor = .orange
leftLabel.translatesAutoresizingMaskIntoConstraints = false
leftLabel.numberOfLines = 0
return leftLabel
}()
private var rightLabel: UILabel = {
let rightLabel = UILabel(frame: .zero)
rightLabel.font = .preferredFont(forTextStyle: .body)
rightLabel.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
rightLabel.backgroundColor = .green
rightLabel.translatesAutoresizingMaskIntoConstraints = false
return rightLabel
}()
override func viewDidLoad() {
super.viewDidLoad()
prepareAndLoadSubViews()
leftLabel.text = "This is left label text that may go in multiple lines"
rightLabel.text = ">"
}
private func prepareAndLoadSubViews() {
let labelContainerView = UIView()
labelContainerView.addSubview(leftLabel)
labelContainerView.addSubview(rightLabel)
NSLayoutConstraint.activate([
leftLabel.leadingAnchor.constraint(equalTo: labelContainerView.leadingAnchor),
leftLabel.topAnchor.constraint(equalTo: labelContainerView.topAnchor),
leftLabel.bottomAnchor.constraint(equalTo: labelContainerView.bottomAnchor),
rightLabel.leadingAnchor.constraint(equalTo: leftLabel.trailingAnchor),
rightLabel.topAnchor.constraint(equalTo: labelContainerView.topAnchor),
rightLabel.bottomAnchor.constraint(equalTo: labelContainerView.bottomAnchor),
rightLabel.trailingAnchor.constraint(equalTo: labelContainerView.trailingAnchor)
])
containerStackView.addArrangedSubview(labelContainerView)
containerStackView.addArrangedSubview(UIView())
}
}