The UI search bar disappears when clicked( possibly NSLayoutConstraint issue) - swift

A quick question, for what should've been an easy implementation.
Im trying to implement a UISearchcontroller and the UIsearchbar property when trying to customize it and set constraints behaves properly appears perfect but the minute i click on the search bar it resets its constraints to nil(guessing based on visual debugger).
Before clicking
and here is the second image which shows what happens when clicked
After clicking
Ive been trying to do this for a day now.
Context: My Main VC is a collection view and another button.
below is the search view specific code, I tried isolating the issue in a playground file and noticed issue starts when i add constraints to it.
var searchController:UISearchController!
private func setupSearchView(){
let viewController = UISearchController(searchResultsController: nil)
viewController.delegate = self
let bar = viewController.searchBar
bar.delegate = self
bar.searchBarStyle = .minimal
bar.translatesAutoresizingMaskIntoConstraints=false
bar.searchTextField.layer.cornerRadius = 20
bar.searchTextField.textColor = .darkGray
bar.searchTextField.backgroundColor = .white
bar.showsCancelButton = false
bar.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
bar.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
bar.layer.shadowOpacity = 1.0
bar.layer.shadowRadius = 0.0
bar.layer.masksToBounds = false
guard let customFont = UIFont(name: "Poppins-SemiBold", size: 14.0) else {
fatalError("""
Failed to load the "CustomFont-Light" font.
Make sure the font file is included in the project and the font name is spelled correctly.
"""
)}
bar.searchTextField.font=customFont
self.searchController = viewController
self.view.addSubview(bar)
bar.isHidden = true
}
func setupContstraints() {
//NSLayoutConstrainst
let searchBar:UISearchBar=searchController!.searchBar
NSLayoutConstraint.activate([
searchButton!.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30),
searchButton!.topAnchor.constraint(equalTo: view.topAnchor, constant: 30),
searchButton!.widthAnchor.constraint(equalToConstant: 50),
searchButton!.heightAnchor.constraint(equalToConstant: 50),
//search bar
searchBar.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
searchBar.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 30),
searchBar.widthAnchor.constraint(equalToConstant: 170.0),
searchBar.heightAnchor.constraint(equalToConstant: 50.0)]
)
Update:
When i gave the search bar fixed width and height(not ideal for different device size) it now appears with the width and height but doesn't obey the top anchor constraint.
See current image
also updated the snippet with current constraints

Give a fixed width and height value for your "SearchBar" object. It will probably be fixed.
It may take up as much width as the cursor when clicked.
Comment out or delete the 2 lines below while giving a fixed height and width for the searchBar.
searchBar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 50),
searchBar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -100)

Rid of the UISearchController and use a UISearchBar instead.
let searchBar = UISearchBar()
let clearButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
setupSearchView()
}
private func setupSearchView(){
searchBar.searchBarStyle = .minimal
searchBar.translatesAutoresizingMaskIntoConstraints=false
searchBar.searchTextField.layer.cornerRadius = 20
searchBar.searchTextField.textColor = .darkGray
searchBar.searchTextField.backgroundColor = .white
searchBar.showsCancelButton = false
searchBar.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.25).cgColor
searchBar.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
searchBar.layer.shadowOpacity = 1.0
searchBar.layer.shadowRadius = 0.0
searchBar.layer.masksToBounds = false
// guard let customFont = UIFont(name: "Poppins-SemiBold", size: 14.0) else {
// fatalError("""
// Failed to load the "CustomFont-Light" font.
// Make sure the font file is included in the project and the font name is spelled correctly.
// """
// )}
// bar.searchTextField.font=customFont
// set your search clear button
clearButton.setImage(UIImage(systemName: "multiply.circle.fill"), for: .normal)
clearButton.tintColor = .red
let searchStack = UIStackView(arrangedSubviews: [searchBar, clearButton])
searchStack.axis = .horizontal
searchStack.distribution = .fill
searchStack.alignment = .fill
searchStack.spacing = 16
searchStack.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(searchStack)
// bar.isHidden = true
NSLayoutConstraint.activate([
clearButton.heightAnchor.constraint(equalTo: clearButton.widthAnchor),
searchStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 30),
searchStack.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 30),
searchStack.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -30),
searchStack.heightAnchor.constraint(equalToConstant: 50)
])
}

Related

Swift, Scroll View, Stack View, programatical Button Creation and Auto Layout causes weird Behaviors / Errors

I have a simple iOS App, containing a Scroll View (scroll horizontally, but mainly as described here: Is it possible for UIStackView to scroll?). By default the Horizontal Stack is empty (spacing = Standard, Distribution is Fill, but also Equal Spacing does not improve this), and I add buttons programatically to it (for example purpose I add a random length string, to have various sizes), using:
filterStackView.translatesAutoresizingMaskIntoConstraints = false
let newButtonConfiguration = UIButton.Configuration.filled()
let newButton = UIButton(configuration: newButtonConfiguration)
newButton.translatesAutoresizingMaskIntoConstraints = false
newButton.setTitle("\(randomString(length: Int.random(in: 5..<25)))", for: .normal)
newButton.setImage(UIImage(systemName: "xmark.circle", withConfiguration: UIImage.SymbolConfiguration(scale: .unspecified)), for: .normal)
newButton.addTarget(self, action: #selector(newButtonPressedAction), for: .touchUpInside)
filterStackView.addArrangedSubview(newButton)
I already tried adding to the button:
newButton.sizeToFit()
newButton.layoutIfNeeded()
or to the View Stack:
filterStackView.sizeToFit()
filterStackView.layoutIfNeeded()
Without any visible change. The weird things happening are:
After adding another one, sizes start changing in a funny way (if buttons are added from Story Board they really respect the width of the text - and Autolayout works fine):
And after playing with adding and removing them for a while:
And in the debug console I see constraints failing badly:
2022-11-01 14:05:48.450435+0100 TestAppSideScroll[20626:640959] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x600001b58c80 'UISV-canvas-connection' H:[UIButton:0x122408660'N6xoAsl']-(0)-| (active, names: '|':UIStackView:0x1505080c0 )>",
"<_UISystemBaselineConstraint:0x600001b586e0 'UISV-spacing' H:[UIButton:0x1223095d0'oHSryjPsjrZA']-(NSLayoutAnchorConstraintSpace(8))-[UIButton:0x122408660'N6xoAsl'] (active)>"
)
Will attempt to recover by breaking constraint
<_UISystemBaselineConstraint:0x600001b586e0 'UISV-spacing' H:[UIButton:0x1223095d0'oHSryjPsjrZA']-(NSLayoutAnchorConstraintSpace(8))-[UIButton:0x122408660'N6xoAsl'] (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
Which actually makes no sense, since the conflict seems to be the spacing constraint with the connection to the canvas constraint? But that I would expect to happen for every single button I add, if that would be the general problem - however there are no real constraints, except the spacing and the snapping of the scroll view.
Any help would be appreciated.
Yeah... something really weird going on there.
My guess would be that UIKit has some sort of algorithm that is trying to apply "readability" to the buttons. Clearly, though, that's not the goal here.
After some quick testing, one way to get around this is to add the button as a subview to a clear "container" view, and then add that container view to the stack view.
Some sample code:
class SampleViewController: UIViewController {
let filterStackViewA: UIStackView = {
let v = UIStackView()
v.spacing = 8
v.distribution = .fill
v.alignment = .center
return v
}()
let filterStackViewB: UIStackView = {
let v = UIStackView()
v.spacing = 8
v.distribution = .fill
v.alignment = .center
return v
}()
let scrollViewA: UIScrollView = {
let v = UIScrollView()
v.backgroundColor = .systemYellow
return v
}()
let scrollViewB: UIScrollView = {
let v = UIScrollView()
v.backgroundColor = .systemOrange
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkGray
let addBtn = UIButton()
addBtn.setTitle("Add Button", for: [])
addBtn.setTitleColor(.white, for: .normal)
addBtn.setTitleColor(.lightGray, for: .highlighted)
addBtn.backgroundColor = .systemGreen
addBtn.layer.cornerRadius = 8
let labelA: UILabel = {
let v = UILabel()
v.textColor = .white
v.font = .systemFont(ofSize: 15.0, weight: .light)
v.text = "Buttons added to \"Container\" views..."
return v
}()
let labelB: UILabel = {
let v = UILabel()
v.textColor = .white
v.font = .systemFont(ofSize: 15.0, weight: .light)
v.text = "Buttons added directly to stack view..."
return v
}()
[addBtn, scrollViewA, scrollViewB, filterStackViewA, filterStackViewB, labelA, labelB].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
view.addSubview(addBtn)
view.addSubview(labelA)
view.addSubview(scrollViewA)
scrollViewA.addSubview(filterStackViewA)
view.addSubview(labelB)
view.addSubview(scrollViewB)
scrollViewB.addSubview(filterStackViewB)
let g = view.safeAreaLayoutGuide
let cgA = scrollViewA.contentLayoutGuide
let fgA = scrollViewA.frameLayoutGuide
let cgB = scrollViewB.contentLayoutGuide
let fgB = scrollViewB.frameLayoutGuide
NSLayoutConstraint.activate([
addBtn.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
addBtn.widthAnchor.constraint(equalToConstant: 200.0),
addBtn.centerXAnchor.constraint(equalTo: g.centerXAnchor),
labelA.topAnchor.constraint(equalTo: addBtn.bottomAnchor, constant: 40.0),
labelA.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
labelA.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
scrollViewA.topAnchor.constraint(equalTo: labelA.bottomAnchor, constant: 8.0),
scrollViewA.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
scrollViewA.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
scrollViewA.heightAnchor.constraint(equalToConstant: 60.0),
filterStackViewA.topAnchor.constraint(equalTo: cgA.topAnchor, constant: 4.0),
filterStackViewA.leadingAnchor.constraint(equalTo: cgA.leadingAnchor, constant: 8.0),
filterStackViewA.trailingAnchor.constraint(equalTo: cgA.trailingAnchor, constant: -8.0),
filterStackViewA.bottomAnchor.constraint(equalTo: cgA.bottomAnchor, constant: -4.0),
filterStackViewA.heightAnchor.constraint(equalTo: fgA.heightAnchor, constant: -8.0),
labelB.topAnchor.constraint(equalTo: filterStackViewA.bottomAnchor, constant: 20.0),
labelB.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
labelB.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
scrollViewB.topAnchor.constraint(equalTo: labelB.bottomAnchor, constant: 8.0),
scrollViewB.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
scrollViewB.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
scrollViewB.heightAnchor.constraint(equalToConstant: 60.0),
filterStackViewB.topAnchor.constraint(equalTo: cgB.topAnchor, constant: 4.0),
filterStackViewB.leadingAnchor.constraint(equalTo: cgB.leadingAnchor, constant: 8.0),
filterStackViewB.trailingAnchor.constraint(equalTo: cgB.trailingAnchor, constant: -8.0),
filterStackViewB.bottomAnchor.constraint(equalTo: cgB.bottomAnchor, constant: -4.0),
filterStackViewB.heightAnchor.constraint(equalTo: fgB.heightAnchor, constant: -8.0),
])
addBtn.addTarget(self, action: #selector(addButton), for: .touchUpInside)
}
#objc func addButton() {
// unwrap optional system image
guard let img = UIImage(systemName: "xmark.circle", withConfiguration: UIImage.SymbolConfiguration(scale: .unspecified))
else { return }
var newButtonConfiguration = UIButton.Configuration.filled()
newButtonConfiguration.title = randomString(length: Int.random(in: 5..<15))
newButtonConfiguration.image = img
// let's add a new button to a "container" view
let newButtonA = UIButton(configuration: newButtonConfiguration)
newButtonA.translatesAutoresizingMaskIntoConstraints = false
let v = UIView()
v.addSubview(newButtonA)
NSLayoutConstraint.activate([
newButtonA.topAnchor.constraint(equalTo: v.topAnchor),
newButtonA.leadingAnchor.constraint(equalTo: v.leadingAnchor),
newButtonA.trailingAnchor.constraint(equalTo: v.trailingAnchor),
newButtonA.bottomAnchor.constraint(equalTo: v.bottomAnchor),
])
// add the container view to the stack view
filterStackViewA.addArrangedSubview(v)
// let's add a new button directly to the stack view
let newButtonB = UIButton(configuration: newButtonConfiguration)
filterStackViewB.addArrangedSubview(newButtonB)
newButtonA.addTarget(self, action: #selector(removeMe(_:)), for: .touchUpInside)
newButtonB.addTarget(self, action: #selector(removeMe(_:)), for: .touchUpInside)
// let's make sure the new button is visible
// note: this is just for example...
// if we rapidly tap and add buttons, this can easily "miss" the last button
// so don't expect this to be "production" code
DispatchQueue.main.async {
let sz = self.scrollViewA.contentSize
let rA = CGRect(x: sz.width - 1.0, y: 0.0, width: 1.0, height: 1.0)
self.scrollViewA.scrollRectToVisible(rA, animated: true)
let szB = self.scrollViewB.contentSize
let rB = CGRect(x: szB.width - 1.0, y: 0.0, width: 1.0, height: 1.0)
self.scrollViewB.scrollRectToVisible(rB, animated: true)
}
}
#objc func removeMe(_ sender: UIButton) {
// get a reference to the tapped button's superview
guard let sv = sender.superview else { return }
var v: UIView!
if sv is UIStackView {
// we tapped a button added directly to the stack view
v = sender
} else {
// we tapped a button that's in a "container" view
v = sv
}
guard let st = v.superview as? UIStackView,
let idx = st.arrangedSubviews.firstIndex(of: v),
filterStackViewA.arrangedSubviews.count > idx,
filterStackViewB.arrangedSubviews.count > idx
else {
print("something's not setup right, so return")
return
}
let bA = filterStackViewA.arrangedSubviews[idx]
let bB = filterStackViewB.arrangedSubviews[idx]
// let's animate the buttons away
bA.isHidden = true
bB.isHidden = true
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
}, completion: { _ in
bA.removeFromSuperview()
bB.removeFromSuperview()
})
}
func randomString(length: Int) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).map{ _ in letters.randomElement()! })
}
}
It will look like this when running:
Each tap of "Add Button" will add a new button to each scrolling-stackView, with the top set using buttons in container views. Tapping any of those buttons will remove it from its stack view, along with its "twin" from the other one.

UILabel is not replacing previous text when view refreshed

I have the following function being called in a ViewController that passes in data to build a list of transactions. Each time the page refreshes, this is called and new data is shown. All is good, except for one remaining frustrating bug: when the new data is loaded the previous balancedOwedAmountLabel.text still shows the previous amount.
So on each refresh, the text is written on top of the previous text each time. This results in a jumbled mess of text each time the page is refreshed. It would be expected to have the previous text removed, and replaced with the new text that I determine.
I've tried manually setting the balancedOwedAmountLabel.text = nil to simply remove it and then reset it each time the data is retrieved and painted, but it won't work. I've also tried removing the subview entirely via balancedOwedAmountLabel.removeFromSuperview() and that doesn't seem to do the trick either.
Since I'm not using a Storyboard I can't connect IBOutlets so I'm having to resort to figuring out how to get around this problem programmatically. How do I go about solving this with the following code?
private func buildTransactionTableView() {
let stackview = UIStackView()
stackview.axis = .vertical
stackview.distribution = .fill
stackview.spacing = 15
self.view.insertSubview(stackview, belowSubview: (self.accountSummaryTableViewController?.view)!)
stackview.translatesAutoresizingMaskIntoConstraints = false
stackview.topAnchor.constraint(equalTo: self.transactionsTableView.bottomAnchor, constant: 0).isActive = true
stackview.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
stackview.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
stackview.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,constant: -30).isActive = true
// full width parent SV
let headlineStackView = UIStackView()
headlineStackView.axis = .horizontal
headlineStackView.alignment = .top
headlineStackView.distribution = .fill
let titleLabel = UILabel(frame: CGRect(x:0,y:0,width:200,height:21))
titleLabel.textAlignment = .left
titleLabel.textColor = .black
titleLabel.font = UIFont.boldSystemFont(ofSize: 14.00)
titleLabel.text = "Transaction History"
headlineStackView.addArrangedSubview(titleLabel)
stackview.addArrangedSubview(headlineStackView)
// SV for balance informations
let balanceStackView = UIStackView()
balanceStackView.axis = .horizontal
balanceStackView.distribution = .fill
headlineStackView.addArrangedSubview(balanceStackView)
let balanceOwedLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
balanceOwedLabel.textAlignment = .right
balanceOwedLabel.textColor = .black
balanceOwedLabel.font = balanceOwedLabel.font.withSize(14.00)
balanceOwedLabel.text = "Balance Owed"
balanceStackView.addArrangedSubview(balanceOwedLabel)
let balancedOwedAmountLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
balancedOwedAmountLabel.textAlignment = .right
balancedOwedAmountLabel.textColor = UIColor(red: 33.00/255, green: 150.00/255, blue: 243.00/255, alpha: 1.00)
balancedOwedAmountLabel.font = UIFont.boldSystemFont(ofSize: 14.00)
var amount = 0.00
for rtp in self.userTransactions! {
if rtp.repaymentDate == nil && rtp.rtpData.amount.value != nil {
let value = Double((rtp.rtpData.amount.value)!)
amount = amount + value!
}
}
balanceStackView.addArrangedSubview(balancedOwedAmountLabel)
// This amount overwrites previous amount on screen when refreshed
// The amount is not being removed and replaced wih this new amount
balancedOwedAmountLabel.text = " $\(String(format: "%.2f", amount))"
let rtpTableVC = UserTransactionsTableViewController(style: .plain)
rtpTableVC.tableView.allowsSelection = false
rtpTableVC.tableView.separatorStyle = .none
rtpTableVC.tableView.isScrollEnabled = true
rtpTableVC.rtpTransactions = self.rtpTransactions
stackview.addArrangedSubview(rtpTableVC.view)
self.userTransactionsTableViewController = rtpTableVC
}
}
It looks like each time this function is called, you create a new stackview and add it in self.view. But I cannot see when you remove previous stackview?
One way of fixing it would be: store reference on current stackview and remove it before adding new one as:
var currentStackView: UIStackView?
private func buildTransactionTableView() {
currentStackView.removeFromSupperView()
let stackview = UIStackView()
currentStackView = stackview
...
}

Swift - Set the height of the item as same as parent in nested UIStackView programmingly

I have a UI with 3 main parts: header, keypad and a button.
Size of header and button are fixed, the remaining area should be occupied by keypad.
Like this:
The keypad part is build up by a nested UIStackView (vertical UIStackView parent, with 4 horizontal UIStackView children), 3 buttons will be added to each of the horizontal UIStackView.
Everything is fine except I would like the buttons to have the same height as the horizontal UIStackView, so that it should be easier for user to click on it.
I have tried
rowStackView.alignment = .fill
or
for button in numberButtons
{
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor.constraint(equalTo: button.superview!.heightAnchor).isActive = true
}
However, the numberPadStackView will be squeezed like following. How should I fix that? Thanks.
These are my codes for now:
headerStackView.axis = .vertical
headerStackView.alignment = .center
headerStackView.setContentHuggingPriority(.defaultHigh, for: .vertical)
headerStackView.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
view.addSubview(headerStackView)
numberPadStackView.axis = .vertical
numberPadStackView.distribution = .fillEqually
numberPadStackView.alignment = .center
view.addSubview(numberPadStackView)
initNumpad()
view.addSubview(requestLabel)
initNumPad()
private func initNumpad()
{
var rowStackView = UIStackView()
numberButtons.removeAll()
for i in 0 ..< 11
{
if i % 3 == 0
{
rowStackView = UIStackView()
rowStackView.axis = .horizontal
rowStackView.distribution = .fillEqually
rowStackView.alignment = .center
rowStackView.setContentHuggingPriority(.defaultLow, for: .vertical)
numberPadRowStackView.append(rowStackView)
numberPadStackView.addArrangedSubview(rowStackView)
}
let button = UIButton()
switch i
{
case 0 ..< 9:
button.setTitle("\(i + 1)", for: .normal)
case 9:
button.setTitle(".", for: .normal)
case 10:
button.setTitle("0", for: .normal)
default:
return
}
button.titleLabel?.textAlignment = .center
button.setContentHuggingPriority(.defaultLow, for: .vertical)
button.backgroundColor = UIColor.random()
numberButtons.append(button)
rowStackView.addArrangedSubview(button)
}
numberPadDeleteImageView.backgroundColor = UIColor.random()
rowStackView.addArrangedSubview(numberPadDeleteImageView)
}
Layout
headerStackView.translatesAutoresizingMaskIntoConstraints = false
numberPadStackView.translatesAutoresizingMaskIntoConstraints = false
requestLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
headerStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 24),
headerStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: Padding),
headerStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -Padding),
numberPadStackView.topAnchor.constraint(equalTo: headerStackView.bottomAnchor, constant: 43),
numberPadStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: Padding),
numberPadStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -Padding),
requestLabel.topAnchor.constraint(equalTo: numberPadStackView.bottomAnchor, constant: 21),
requestLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 28),
requestLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -28),
requestLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -5),
requestLabel.heightAnchor.constraint(equalToConstant: 40),
])
for rowStackView in numberPadRowStackView
{
rowStackView.translatesAutoresizingMaskIntoConstraints = false
rowStackView.widthAnchor.constraint(equalTo: numberPadStackView.widthAnchor).isActive = true
}
Couple notes...
Setting Content Hugging Priority on a UIStackView is generally not going to give you the expected results. That's because the stack view is arranging its subviews (based on the stack view's Alignment and Distribution properties). The Content Hugging Priority of the stack view's arranged subviews will be the controlling factor.
As is obvious, if I lay out 4 labels, vertically constrained top-to-bottom, each having the same Content Hugging priority (such as the default 250), I'll get something like this in Storyboard (note the Red layout-problem indicator):
and at runtime it may look like this:
Auto-layout is going to respect the (intrinsic) Height for Labels 1, 2 and 4, and then stretch Label 3 to complete the layout.
If I embed the top two and bottom two labels each in vertical stack views...
Storyboard will look like this (again, note the Red layout-problem indicator):
and we get the same thing at run-time:
Even if I set the Content Hugging Priority of the top stack view to 1000, it won't make a difference -- because auto-layout is using the arranged subviews to decide what to do.
So, for your layout, divide your screen into *three layout elements:
the "header" section
the "numberPad" section
the "process" section
and then tell auto-layout you want the Header and Process elements to maintain their heights, and allow the numberPad to stretch.
Since UI elements default to Hugging Priority of 250, probably the easiest way to manage that is reduce the Hugging Priority of the numberPad buttons.
Here is some example code. I'm not sure how you're laying out your "headerStackView" as it doesn't really look like it would lend itself to a stack view... so, I laid it out as a header UIView:
class WithNumpadViewController: UIViewController {
// three "parts" to our layout
let headerView = UIView()
let numberPadStackView = UIStackView()
let requestLabel = UILabel()
// data labels to be filled-in
let currencyLabel = UILabel()
let currValueLabel = UILabel()
let balanceLabel = UILabel()
// however you're using this
var numberButtons: [UIButton] = []
let Padding: CGFloat = 16
override func viewDidLoad() {
super.viewDidLoad()
if let vc = self.navigationController?.viewControllers.first {
vc.navigationItem.title = "Wallet"
}
self.navigationController?.navigationBar.barTintColor = .black
self.navigationController?.navigationBar.tintColor = .white
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
title = "Withdraw"
// add "three parts" to view
[headerView, numberPadStackView, requestLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain headerView to Top / Leading / Trailing (safe-area)
// let its content determine its height
headerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
headerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
headerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
// constrain numberPad 40-pts from Bottom headerView
numberPadStackView.topAnchor.constraint(equalTo: headerView.bottomAnchor, constant: 40),
// Leading / Trailing with Padding
numberPadStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: Padding),
numberPadStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -Padding),
// constrain requestLabel 21-pts from Bottom of numberPad
requestLabel.topAnchor.constraint(equalTo: numberPadStackView.bottomAnchor, constant: 21),
// Leading / Trailing with 28-pts padding
requestLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 28),
requestLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -28),
// Bottom 5-pts from Bottom (safe-area)
requestLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -5),
// explicit Height of 40-pts
requestLabel.heightAnchor.constraint(equalToConstant: 40),
])
// setup contents of headerView
initHeader()
// setup contents of numberPad
initNumpad()
// properties for requestLabel
requestLabel.backgroundColor = .black
requestLabel.textColor = .white
requestLabel.textAlignment = .center
requestLabel.text = "Process"
// fill-in data labels
currencyLabel.text = "HKD"
currValueLabel.text = "0"
balanceLabel.text = "Balance: HKD 2 (Available)"
// maybe add number pad button actions here?
numberButtons.forEach { b in
b.addTarget(self, action: #selector(self.numberPadButtonTapped(_:)), for: .touchUpInside)
}
}
#objc func numberPadButtonTapped(_ btn: UIButton) -> Void {
let t = btn.currentTitle ?? "Delete"
print("Tapped:", t)
// do what you want based on which button was tapped
}
private func initHeader()
{
// not clear how you're setting up your "header"
// so I'll guess at it
// view properties
headerView.backgroundColor = .black
headerView.clipsToBounds = true
headerView.layer.cornerRadius = 24
headerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
// static text label
let transferToLabel = UILabel()
transferToLabel.text = "Transfer to..."
transferToLabel.font = UIFont.systemFont(ofSize: 14.0, weight: .regular)
// PayPal button
let payPalButton = UIButton()
payPalButton.setTitle("PayPal", for: [])
payPalButton.backgroundColor = .white
payPalButton.setTitleColor(.blue, for: [])
// data label fonts
currencyLabel.font = UIFont.systemFont(ofSize: 28.0, weight: .bold)
currValueLabel.font = currencyLabel.font
balanceLabel.font = UIFont.systemFont(ofSize: 11.0, weight: .regular)
// label text color
[transferToLabel, currencyLabel, currValueLabel, balanceLabel].forEach {
$0.textColor = .white
}
// horizontal stack to hold currencyLabel, currValueLabel
let currValStack = UIStackView()
currValStack.axis = .horizontal
currValStack.spacing = 12
// vertical stack to hold currValStack, balanceLabel
let innerVStack = UIStackView()
innerVStack.axis = .vertical
innerVStack.alignment = .center
innerVStack.spacing = 2
// add labels to Horizontal stack
currValStack.addArrangedSubview(currencyLabel)
currValStack.addArrangedSubview(currValueLabel)
// add horizontal stack and balanceLabel to vertical stack
innerVStack.addArrangedSubview(currValStack)
innerVStack.addArrangedSubview(balanceLabel)
// view to hold vertical stack (so we can center it vertically)
let innerView = UIView()
// add vertical stack to innerView
innerView.addSubview(innerVStack)
// add elements to headerView
headerView.addSubview(transferToLabel)
headerView.addSubview(payPalButton)
headerView.addSubview(innerView)
// we'll be applying constraints
[headerView, transferToLabel, payPalButton, currencyLabel, currValueLabel, balanceLabel,
innerView, innerVStack].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
NSLayoutConstraint.activate([
// "Transfer to..." label Top: Padding, leading: Padding
transferToLabel.topAnchor.constraint(equalTo: headerView.topAnchor, constant: Padding),
transferToLabel.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: Padding),
// payPal button Top: Padding to transfer label Bottom
// Leading / Trailing to Leading / Trailing with Padding
payPalButton.topAnchor.constraint(equalTo: transferToLabel.bottomAnchor, constant: Padding),
payPalButton.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: Padding),
payPalButton.trailingAnchor.constraint(equalTo: headerView.trailingAnchor, constant: -Padding),
// payPalButton explicit height
payPalButton.heightAnchor.constraint(equalToConstant: 50.0),
// innerView Top: 0 to payPal button Bottom
// Leading / Trailing to Leading / Trailing with Padding
// Bottom: 0
innerView.topAnchor.constraint(equalTo: payPalButton.bottomAnchor, constant: 0.0),
innerView.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: Padding),
innerView.trailingAnchor.constraint(equalTo: headerView.trailingAnchor, constant: -Padding),
innerView.bottomAnchor.constraint(equalTo: headerView.bottomAnchor, constant: 0.0),
// innerVStack Top / Bottom to innerView Top / Bottom with 24-pts padding
// centerX
innerVStack.topAnchor.constraint(equalTo: innerView.topAnchor, constant: 32.0),
innerVStack.bottomAnchor.constraint(equalTo: innerView.bottomAnchor, constant: -32.0),
innerVStack.centerXAnchor.constraint(equalTo: innerView.centerXAnchor, constant: 0.0),
])
}
private func initNumpad()
{
numberButtons.removeAll()
// numberPad stack properties
numberPadStackView.axis = .vertical
numberPadStackView.alignment = .fill
numberPadStackView.distribution = .fillEqually
numberPadStackView.spacing = 4
// a little more logical way to manage button layout
let buttonLabels: [[String]] = [
["1", "2", "3"],
["4", "5", "6"],
["7", "8", "9"],
[".", "0", "<"],
]
// adjust as desired
let btnFontSize: CGFloat = 28
buttonLabels.forEach { thisRowLabels in
// create a "row" stack view
let rowStack = UIStackView()
rowStack.axis = .horizontal
rowStack.alignment = .fill
rowStack.distribution = .fillEqually
// same horizontal spacing as "number pad" stack's vertical spacing
rowStack.spacing = numberPadStackView.spacing
// for each number string
thisRowLabels.forEach { s in
// create button
let btn = UIButton()
if s == "<" {
// if it's the "delete button"
// set image here
let iconCfg = UIImage.SymbolConfiguration(pointSize: btnFontSize, weight: .bold, scale: .large)
if let normIcon = UIImage(systemName: "delete.left", withConfiguration: iconCfg)?.withTintColor(.black, renderingMode: .alwaysOriginal),
let highIcon = UIImage(systemName: "delete.left", withConfiguration: iconCfg)?.withTintColor(.lightGray, renderingMode: .alwaysOriginal)
{
btn.setImage(normIcon, for: .normal)
btn.setImage(highIcon, for: .highlighted)
}
} else {
// set number pad button title
btn.setTitle(s, for: [])
}
// number pad button properties
btn.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
btn.setTitleColor(.black, for: .normal)
btn.setTitleColor(.lightGray, for: .highlighted)
btn.titleLabel?.font = UIFont.systemFont(ofSize: btnFontSize, weight: .bold)
// let's give 'em a rounded-corner border
btn.layer.borderColor = UIColor.blue.cgColor
btn.layer.borderWidth = 1
btn.layer.cornerRadius = 8
// allow buttons to stretch vertically!!!
btn.setContentHuggingPriority(UILayoutPriority(rawValue: 249), for: .vertical)
// add button to this row stack
rowStack.addArrangedSubview(btn)
// add button to numberButtons array
numberButtons.append(btn)
}
// add this rowStack to the number pad stack
numberPadStackView.addArrangedSubview(rowStack)
}
}
}
The result, on iPhone 8:
iPhone 11:
and iPhone 11 Pro Max:

Reposition in y-direction a custom navigationBar titleView

Using Swift5.1.3, XCode11.3, iOS13.3,
I try to reposition a custom navigationBar titleView.
Creating the custom view and adding it to my navigationBar works fine. (see code below)
Here an example: Please only consider the DarkGray NavigationBar on top with a Name-Label and a yellow round Image. The label and image shall be moved in y-direction!
The example on the left, I have successfully running. The example on the right I try to achieve. But without luck so far.
There is one missing thing I am struggling with since 4 hours.
How do I adjust the y-position (or .topAnchor constant offset) of a custom navigationBar titleView ???
The crash-message says:
'Unable to activate constraint with anchors
<NSLayoutYAxisAnchor:0x6000033ac900 "UIStackView:0x7fdcced39ea0.top"> and
<NSLayoutYAxisAnchor:0x6000033644c0 "UIView:0x7fdcd412ba20.top"> because they
have no common ancestor. Does the constraint or its anchors reference items
in different view hierarchies? That's illegal.'
Here is my code (please note the comment with the many exclamation marks - that is the y-offset trial and crash position of my code):
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// ...
// set up navigationItem and navigationController look and feeel
navigationController?.set_iOS12_lookAndFeel()
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationItem.largeTitleDisplayMode = .always
// create NavigationBar.titleView StackView (consisting of a label and a button)
let titleStackView = UIStackView(frame: CGRect(origin: .zero, size: CGSize(width: self.view.bounds.width, height: 88.0)))
titleStackView.isUserInteractionEnabled = true
titleStackView.axis = .horizontal
titleStackView.alignment = .center
titleStackView.spacing = 10.0
// stackView label
let labelWidth: CGFloat = UIScreen.main.bounds.width - 16.0 - 10.0 - 36.0 - 16.0 // FullScreenWidth minus (Leading + Spacing + ButtonWidth + Trailing)
let label = UILabel()
label.font = AppConstants.Font.NavBar_TitleFont
label.text = self.profileName
label.textColor = .white
label.tintColor = .white
// position label
label.translatesAutoresizingMaskIntoConstraints = false
label.widthAnchor.constraint(equalToConstant: labelWidth).isActive = true
// stackView button
let buttonWidth: CGFloat = 36.0
let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: buttonWidth, height: buttonWidth)))
button.setImage(self.profileImageView.image, for: .normal)
button.isUserInteractionEnabled = true
button.addTarget(self, action: #selector(self.callProfileBtnMethod), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 36, height: 36)
button.layer.cornerRadius = button.frame.size.width / 2
button.layer.masksToBounds = false
button.clipsToBounds = true
// position button
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: buttonWidth).isActive = true
button.heightAnchor.constraint(equalToConstant: buttonWidth).isActive = true
// add label and button to stackView
titleStackView.addArrangedSubview(label)
titleStackView.addArrangedSubview(button)
// position titleStackView
titleStackView.translatesAutoresizingMaskIntoConstraints = false
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Here the code crashes !!!!!!!
titleStackView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100.0).isActive = true
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// position cockpitHeaderView (equal in size and position to titleStackView)
let cockpitHeaderView = UIView(frame: CGRect(origin: .zero, size: CGSize(width: self.view.bounds.width, height: 88.0)))
cockpitHeaderView.backgroundColor = .green
cockpitHeaderView.isUserInteractionEnabled = true
cockpitHeaderView.addSubview(titleStackView)
cockpitHeaderView.leadingAnchor.constraint(equalTo: titleStackView.leadingAnchor, constant: 0.0).isActive = true
cockpitHeaderView.topAnchor.constraint(equalTo: titleStackView.topAnchor, constant: 0.0).isActive = true
cockpitHeaderView.trailingAnchor.constraint(equalTo: titleStackView.trailingAnchor, constant: 0.0).isActive = true
cockpitHeaderView.bottomAnchor.constraint(equalTo: titleStackView.bottomAnchor, constant: 0.0).isActive = true
// finally replace NavBar title by custom cockpitHeaderView
self.title = ""
self.navigationItem.titleView = cockpitHeaderView
}
How can I move the titleView correctly ???

Cannot see Buttons in UIScrollView

I was making a list in the form of scrollview in swift where the view consists of various types such as labels, button etc.
However when i added the button to the subview, they were not displayed although all other labels etc were displayed. I also tried messing around in the constraints and anchors.
On the other hand when i added the same button to self.view.addsubview instead of scrollview.addsubview, they were displayed just not scrolling since not a part of the scrollview anymore.
I even removed the label to make sure that the buttons were not being overlapped(didn't work either)
I also tried to see the code in "code debug hierarchy " (3D mode), i couldn't see the button there either even though i had added it
Below is my code with an example of label, scrollview and button. It be great if anyone could provide any insights.....thanks either way....
................scrollview..........................
var editInfoView : UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.contentSize.height = 700
view.backgroundColor = tableBackGroundColor
view.frame = CGRect(x: 0, y: 220, width: 375, height: 400)
return view
}()
.......................label...................
vehicleNumberLabel.translatesAutoresizingMaskIntoConstraints = false
vehicleNumberLabel.textColor = .white
vehicleNumberLabel.text = "Vehicle Number"
vehicleNumberLabel.textAlignment = .left
editInfoView.addSubview(vehicleNumberLabel)
vehicleNumberLabel.leftAnchor.constraint(equalTo: editInfoView.leftAnchor).isActive = true
vehicleNumberLabel.topAnchor.constraint(equalTo: editInfoView.topAnchor, constant: 100).isActive = true
vehicleNumberLabel.widthAnchor.constraint(equalToConstant: 160).isActive = true
vehicleNumberLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
.....................button................................
vehicleNumberButton.translatesAutoresizingMaskIntoConstraints = false
vehicleNumberButton.setTitleColor(tableTextColor, for: .normal)
vehicleNumberButton.setTitle("Vehicle Number", for: .normal)
vehicleNumberButton.tintColor = tableTextColor
vehicleNumberButton.backgroundColor = tableTextColor
editInfoView.addSubview(vehicleNumberButton)
vehicleNumberButton.rightAnchor.constraint(equalTo: editInfoView.rightAnchor).isActive = true
vehicleNumberButton.topAnchor.constraint(equalTo: editInfoView.topAnchor, constant: 400).isActive = true
vehicleNumberButton.widthAnchor.constraint(equalToConstant: 600).isActive = true
vehicleNumberButton.heightAnchor.constraint(equalToConstant: 255).isActive = true
Although I cannot determine the root cause of your issue with the code and explanation provided I suspect the frame of your UIScrollView() is zero after viewDidAppear(_:) adding subviews to a CGRect.zero can cause some strange behavior with the layout engine. When we create constraints programmatically we are creating a combination of inequalities, equalities, and priorities to restrict the view to a particular frame. If a the value of these constraint equations is incorrect it changes how your relating views appear. Its good practice to avoid the use of leftAnchor and rightAnchor as well, because views may flip direction based on language (writing direction) and user settings.
ViewController.swift
import UIKit
class ViewController: UIViewController {
var editInfoScrollView : UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.isUserInteractionEnabled = true
view.alwaysBounceVertical = true
view.isScrollEnabled = true
view.contentSize.height = 700
view.backgroundColor = UIColor.red.withAlphaComponent(0.3)
// Does nothing because `translatesAutoresizingMaskIntoConstraints = false`
// Instead, set the content size after activating constraints in viewDidAppear
//view.frame = CGRect(x: 0, y: 220, width: 375, height: 400)
return view
}()
var vehicleNumberLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.black
label.text = "Vehicle Number"
label.textAlignment = .left
return label
}()
lazy var vehicleNumberButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.tag = 1
button.setTitleColor(UIColor.black, for: .normal)
button.setTitle("Go to Vehicle", for: .normal)
button.tintColor = UIColor.white
button.backgroundColor = UIColor.clear
button.layer.cornerRadius = 30 // about half of button.frame.height
button.layer.borderColor = UIColor.black.cgColor
button.layer.borderWidth = 2.0
button.layer.masksToBounds = true
button.addTarget(self, action: #selector(handelButtons(_:)), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
self.setupSubviews()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.editInfoScrollView.contentSize = CGSize(width: self.view.frame.width, height: 700.0)
}
func setupSubviews() {
self.view.addSubview(editInfoScrollView)
editInfoScrollView.addSubview(vehicleNumberLabel)
editInfoScrollView.addSubview(vehicleNumberButton)
let spacing: CGFloat = 12.0
let constraints:[NSLayoutConstraint] = [
editInfoScrollView.widthAnchor.constraint(equalTo: self.view.widthAnchor),
editInfoScrollView.heightAnchor.constraint(equalToConstant: 400.0),
editInfoScrollView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
editInfoScrollView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 220.0),
vehicleNumberLabel.leadingAnchor.constraint(equalTo: editInfoScrollView.leadingAnchor, constant: spacing),
vehicleNumberLabel.trailingAnchor.constraint(equalTo: editInfoScrollView.trailingAnchor, constant: -spacing),
vehicleNumberLabel.centerXAnchor.constraint(equalTo: editInfoScrollView.centerXAnchor, constant: -50),
vehicleNumberLabel.heightAnchor.constraint(equalToConstant: 75.0),
vehicleNumberButton.widthAnchor.constraint(equalTo: editInfoScrollView.widthAnchor, multiplier: 0.66),
vehicleNumberButton.heightAnchor.constraint(equalToConstant: 65.0),
vehicleNumberButton.topAnchor.constraint(equalTo: vehicleNumberLabel.bottomAnchor, constant: spacing),
vehicleNumberButton.centerXAnchor.constraint(equalTo: editInfoScrollView.centerXAnchor),
]
NSLayoutConstraint.activate(constraints)
}
#objc func handelButtons(_ sender: UIButton) {
switch sender.tag {
case 0:
print("Default button tag")
case 1:
print("vehicleNumberButton was tapped")
default:
print("Nothing here yet")
}
}
}