Let me explain what I want to achieve. I want to create a slider and horizontal list of buttons inside UIStackView and UIScrollView so that buttons can scroll and then UISlider and UIScrollview will be placed inside vertical UIStackView. But the problem is I can scroll the UISlider but the buttons horizontally seem stuck or overlapped with the UIScrolView horizontal and it was not working I tried everything but not able to fix it. I wanted to do it programmatically. Any Help is really helpful
class ViewController: UIViewController {
private var stackView: UIStackView!
private var stackViewNew: UIStackView!
let x: CGFloat = 10
let width: CGFloat = UIScreen.main.bounds.width - 20
var y: CGFloat = 10
var i = 0
let step:Float=10
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = true
v.frame = CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: 200)
return v
}()
private var stackViewFilter: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = true
v.axis = .vertical
v.backgroundColor = .black
v.alpha = 0.8
v.frame = CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: 330)
v.frame.origin = CGPoint(x:0 , y: UIScreen.main.bounds.height - 330)
v.distribution = .equalSpacing
v.spacing = 10.0
return v
}()
let horizontalStackView : UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = true
v.axis = .horizontal
v.backgroundColor = .systemPink
v.frame = CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: 200)
v.distribution = .equalSpacing
v.spacing = 10.0
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
createBottomFilter()
}
@objc func createBottomFilter(){
/*---------- Slider Section ----------*/
let mySlider = UISlider(frame:CGRect(x: 40, y: 10, width: 200, height: 60))
mySlider.minimumValue = 0
mySlider.maximumValue = 100
mySlider.isContinuous = true
mySlider.tintColor = UIColor.green
mySlider.addTarget(self, action: #selector(self.sliderValueDidChange(_:)), for: .valueChanged)
mySlider.translatesAutoresizingMaskIntoConstraints = true
UIView.animate(withDuration: 0.8) {
mySlider.setValue(80.0, animated: true)
}
self.view.addSubview(stackViewFilter)
stackViewFilter.addArrangedSubview(mySlider)
stackViewFilter.addArrangedSubview(scrollView)
self.view.addSubview(scrollView)
mySlider.leadingAnchor.constraint(equalTo: stackViewFilter.leadingAnchor, constant: 8).isActive = true
mySlider.trailingAnchor.constraint(equalTo: stackViewFilter.trailingAnchor, constant: 8).isActive = true
mySlider.topAnchor.constraint(equalTo: stackViewFilter.bottomAnchor, constant: 10).isActive = true
constraintBottom = mySlider.bottomAnchor.constraint(equalTo: scrollView.topAnchor, constant: 40)
constraintBottom?.isActive = true
scrollView.leftAnchor.constraint(equalTo: stackViewFilter.leftAnchor, constant: 0.0).isActive = true
scrollView.topAnchor.constraint(equalTo: mySlider.bottomAnchor, constant: 8.0).isActive = true
scrollView.rightAnchor.constraint(equalTo: stackViewFilter.rightAnchor, constant: 80.0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 8.0).isActive = true
// add the stack view to the scroll view
scrollView.addSubview(horizontalStackView)
horizontalStackView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0.0).isActive = true
horizontalStackView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0.0).isActive = true
horizontalStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -30.0).isActive = true
let b = generateButton(title: "Btn 1", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
b.translatesAutoresizingMaskIntoConstraints = true
let b1 = generateButton(title: "Btn 2", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
b1.translatesAutoresizingMaskIntoConstraints = true
let b2 = generateButton(title: "Btn 3", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
b2.translatesAutoresizingMaskIntoConstraints = true
let b3 = generateButton(title: "Btn 4", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
b3.translatesAutoresizingMaskIntoConstraints = true
let b4 = generateButton(title: "Btn 5", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
b4.translatesAutoresizingMaskIntoConstraints = true
let b5 = generateButton(title: "Btn 6", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
b5.translatesAutoresizingMaskIntoConstraints = true
horizontalStackView.addArrangedSubview(b)
horizontalStackView.addArrangedSubview(b1)
horizontalStackView.addArrangedSubview(b2)
horizontalStackView.addArrangedSubview(b3)
horizontalStackView.addArrangedSubview(b4)
horizontalStackView.addArrangedSubview(b5)
horizontalStackView.alignment = .center
}
@objc func generateButton(title: String, selectedTitle: String? = nil, iconName: String, scaledToSize newSize: CGSize) -> UIButton {
let iconName: UIImage? = imageWithImage(UIImage(named: iconName), scaledToSize:CGSize(width: newSize.width, height: newSize.height))
iconName?.withTintColor(.white)
let button = UIButton.vertical(padding: 3)
button.frame = CGRect(x: x, y: y, width: width, height: 80)
button.setImage(iconName, for: .normal)
button.layer.zPosition = 1
button.setTitle(title, for: .normal)
button.setTitle(selectedTitle, for: .selected)
self.view.addSubview(button)
i += 1
y += button.frame.height
return button
}
}
class VerticalButton: UIButton {
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
let titleRect = super.titleRect(forContentRect: contentRect)
let imageRect = super.imageRect(forContentRect: contentRect)
return CGRect(x: 0,
y: contentRect.height - (contentRect.height - padding - imageRect.size.height - titleRect.size.height) / 2 - titleRect.size.height,
width: contentRect.width,
height: titleRect.height)
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let imageRect = super.imageRect(forContentRect: contentRect)
let titleRect = self.titleRect(forContentRect: contentRect)
return CGRect(x: contentRect.width/2.0 - imageRect.width/2.0,
y: (contentRect.height - padding - imageRect.size.height - titleRect.size.height) / 2,
width: imageRect.width,
height: imageRect.height )
}
private let padding: CGFloat
init(padding: CGFloat) {
self.padding = padding
super.init(frame: .zero)
self.titleLabel?.textAlignment = .center
}
required init?(coder aDecoder: NSCoder) { fatalError() }
}
extension UIButton {
static func vertical(padding: CGFloat) -> UIButton {
return VerticalButton(padding: padding)
}}
It's a little difficult, because you didn't show an image of what you want to achieve.
Also, your code is missing you imageWithImage(...)
function, so we can't run it directly to see exactly what you're getting.
However, this may help you on your way...
You are doing many things incorrectly -- mixing explicit frame settings with stack views (which use auto-layout); adding views to the wrong place; setting constraints where you shouldn't be; etc.
Hers is your code to hopefully get close to what you're after. I added comments explaining what should't be there, and commented out your existing code so you can see the differences:
class ViewController: UIViewController {
var i = 0
// these will not be used
// private var stackView: UIStackView!
// private var stackViewNew: UIStackView!
// let x: CGFloat = 10
// let width: CGFloat = UIScreen.main.bounds.width - 20
// var y: CGFloat = 10
// let step:Float=10
let scrollView: UIScrollView = {
let v = UIScrollView()
// we will want to use auto-layout
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
private var stackViewFilter: UIStackView = {
let v = UIStackView()
// we will want to use auto-layout
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.backgroundColor = .black
v.alpha = 0.8
// use .fill instead of .equalSpacing
v.distribution = .fill
v.spacing = 10.0
return v
}()
let horizontalStackView : UIStackView = {
let v = UIStackView()
// we will want to use auto-layout
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .horizontal
v.backgroundColor = .systemPink
// you want the buttons to be equal sizes,
// so use .fillEqually instead of .equalSpacing
v.distribution = .fill
v.spacing = 10.0
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
createBottomFilter()
}
@objc func createBottomFilter(){
/*---------- Slider Section ----------*/
// we will want to use auto-layout
// so no need to set a frame here
//let mySlider = UISlider(frame:CGRect(x: 40, y: 10, width: 200, height: 60))
let mySlider = UISlider()
mySlider.translatesAutoresizingMaskIntoConstraints = false
mySlider.minimumValue = 0
mySlider.maximumValue = 100
mySlider.isContinuous = true
mySlider.tintColor = UIColor.green
mySlider.addTarget(self, action: #selector(self.sliderValueDidChange(_:)), for: .valueChanged)
UIView.animate(withDuration: 0.8) {
mySlider.setValue(80.0, animated: true)
}
// respect safe-area
let g = view.safeAreaLayoutGuide
self.view.addSubview(stackViewFilter)
// constrain stackViewFilter
NSLayoutConstraint.activate([
stackViewFilter.leadingAnchor.constraint(equalTo: g.leadingAnchor),
stackViewFilter.trailingAnchor.constraint(equalTo: g.trailingAnchor),
stackViewFilter.bottomAnchor.constraint(equalTo: g.bottomAnchor),
stackViewFilter.heightAnchor.constraint(equalToConstant: 330.0),
])
stackViewFilter.addArrangedSubview(mySlider)
stackViewFilter.addArrangedSubview(scrollView)
// just added scrollView as an arrangedSubview of stackViewFilter
// so don't add it to the view
//self.view.addSubview(scrollView)
// slider is in a stack view, so don't set any positioning constraints
//mySlider.leadingAnchor.constraint(equalTo: stackViewFilter.leadingAnchor, constant: 8).isActive = true
//mySlider.trailingAnchor.constraint(equalTo: stackViewFilter.trailingAnchor, constant: 8).isActive = true
//mySlider.topAnchor.constraint(equalTo: stackViewFilter.bottomAnchor, constant: 10).isActive = true
//constraintBottom = mySlider.bottomAnchor.constraint(equalTo: scrollView.topAnchor, constant: 40)
//constraintBottom?.isActive = true
// scrollView is in a stack view, so don't set any positioning constraints
//scrollView.leftAnchor.constraint(equalTo: stackViewFilter.leftAnchor, constant: 0.0).isActive = true
//scrollView.topAnchor.constraint(equalTo: mySlider.bottomAnchor, constant: 8.0).isActive = true
//scrollView.rightAnchor.constraint(equalTo: stackViewFilter.rightAnchor, constant: 80.0).isActive = true
//scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 8.0).isActive = true
// but we can set the scrollView's height constraint here
scrollView.heightAnchor.constraint(equalToConstant: 200.0).isActive = true
// add the stack view to the scroll view
scrollView.addSubview(horizontalStackView)
// constrain scrollView contents to the scrollView's contentLayoutGuide
//horizontalStackView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0.0).isActive = true
//horizontalStackView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0.0).isActive = true
//horizontalStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -30.0).isActive = true
NSLayoutConstraint.activate([
horizontalStackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
horizontalStackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
horizontalStackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
horizontalStackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: 30.0),
])
// views added to stackView as arrangedSubview automatically use auto-layout
// so no sense setting .translatesAutoresizingMaskIntoConstraints = true
let b = generateButton(title: "Btn 1", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
//b.translatesAutoresizingMaskIntoConstraints = true
let b1 = generateButton(title: "Btn 2", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
//b1.translatesAutoresizingMaskIntoConstraints = true
let b2 = generateButton(title: "Btn 3", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
//b2.translatesAutoresizingMaskIntoConstraints = true
let b3 = generateButton(title: "Btn 4", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
//b3.translatesAutoresizingMaskIntoConstraints = true
let b4 = generateButton(title: "Btn 5", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
//b4.translatesAutoresizingMaskIntoConstraints = true
let b5 = generateButton(title: "Btn 6", selectedTitle: "Filter \(i)", iconName: "hellen", scaledToSize:CGSize(width: 90.0, height: 90.0))
//b5.translatesAutoresizingMaskIntoConstraints = true
horizontalStackView.addArrangedSubview(b)
horizontalStackView.addArrangedSubview(b1)
horizontalStackView.addArrangedSubview(b2)
horizontalStackView.addArrangedSubview(b3)
horizontalStackView.addArrangedSubview(b4)
horizontalStackView.addArrangedSubview(b5)
// alignment should be .fill, not .center
horizontalStackView.alignment = .fill
// because we set horizontalStackView.distribution = .fillEqually
// we only need to set a width constraint on the first button
b.widthAnchor.constraint(equalToConstant: 90.0).isActive = true
// buttons should all be 90x90 ?
[b, b1, b2, b3, b4, b5].forEach { btn in
btn.heightAnchor.constraint(equalTo: btn.widthAnchor).isActive = true
}
}
@objc func generateButton(title: String, selectedTitle: String? = nil, iconName: String, scaledToSize newSize: CGSize) -> UIButton {
let iconName: UIImage? = imageWithImage(UIImage(named: iconName), scaledToSize:CGSize(width: newSize.width, height: newSize.height))
iconName?.withTintColor(.white)
let button = UIButton.vertical(padding: 3)
// buttons in stack view will use auto-layout,
// so no need to set frames here
//button.frame = CGRect(x: x, y: y, width: width, height: 80)
button.setImage(iconName, for: .normal)
button.layer.zPosition = 1
button.setTitle(title, for: .normal)
button.setTitle(selectedTitle, for: .selected)
// button will be added to stack view
//self.view.addSubview(button)
i += 1
// not sure why this was here to begin with...
// you want a horizontal row of buttons, so changing the
// y position makes no sense
//y += button.frame.height
return button
}
}
class VerticalButton: UIButton {
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
let titleRect = super.titleRect(forContentRect: contentRect)
let imageRect = super.imageRect(forContentRect: contentRect)
return CGRect(x: 0,
y: contentRect.height - (contentRect.height - padding - imageRect.size.height - titleRect.size.height) / 2 - titleRect.size.height,
width: contentRect.width,
height: titleRect.height)
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let imageRect = super.imageRect(forContentRect: contentRect)
let titleRect = self.titleRect(forContentRect: contentRect)
return CGRect(x: contentRect.width/2.0 - imageRect.width/2.0,
y: (contentRect.height - padding - imageRect.size.height - titleRect.size.height) / 2,
width: imageRect.width,
height: imageRect.height )
}
private let padding: CGFloat
init(padding: CGFloat) {
self.padding = padding
super.init(frame: .zero)
self.titleLabel?.textAlignment = .center
}
required init?(coder aDecoder: NSCoder) { fatalError() }
}
extension UIButton {
static func vertical(padding: CGFloat) -> UIButton {
return VerticalButton(padding: padding)
}
}
Here's how it looks, using a system "doc" image for the buttons, since I don't know what you're doing with iconName
... the horizontal buttons can be scrolled:
If that's close to what you want, you should be able to tweak values after reviewing the code.