I have a UIScrollview with horizontal pagination, inside which there are five different view. These are scrolling perfectly as required. And now i also have five buttons on the top of my screen, on button action the scrollview will scroll to the required page(for example if user tap on button 3, the scrollview will scroll to third page). So now I want a small view to work as a selectorView(that is if user scrolling to next page the selector view should move to next button during scroll)just below the five buttons. This is also working fine to some extent but there is small issue specially in iPad devices. The issue is that my selectorView is not finishing in the center of required button. How can it be in the center of required button. I have used below code to move the selectorView.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let newxPosition = scrollView.contentOffset.x
if UIDevice().userInterfaceIdiom == .phone {
UIView.animate(withDuration: 0.1) { [self] in
self.movingSelectorView.frame.origin.x = newxPosition/5 + (movingViewXConstant ?? 25)
}
} else if UIDevice().userInterfaceIdiom == .pad {
UIView.animate(withDuration: 0.1) { [self] in
self.movingSelectorView.frame.origin.x = newxPosition/5 + (movingViewXConstant ?? 75)
}
}
}
Please see the image uploaded for better understanding. The five buttons are not in the scrollview as well as the movingSelectorView is also not inside the scrollView.
Without getting details from you on your view hierarchy and current code, I'll guess at something that you may find useful.
Let's:
We'll set the initial frame of the selector view to a size of (20, 3)
, and position it under the title label of the "Day" buttons.
The center.x
value of the selector view frame will start at one-half of the width of the first Day button.
When the scroll view scrolls - whether being dragged or by calling .scrollRectToVisible
on a button tap - we'll get the percentage it has scrolled, and update the center.x
of the selector view to the same percentage of the total buttons width, offset by the "one-half button width" value.
So, example code:
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView()
let btnStack = UIStackView()
let movingSelectorView = UIView()
// this will be one-half of the width of a "Day" button
var btnCenterOffset: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
// stack view to hold the "Day" buttons
btnStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(btnStack)
btnStack.distribution = .fillEqually
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.isPagingEnabled = true
// stack view to hold the 5 "Day" views in the scroll view
let contentStackView = UIStackView()
contentStackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentStackView)
let g = view.safeAreaLayoutGuide
let cg = scrollView.contentLayoutGuide
let fg = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
btnStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
btnStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
btnStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
btnStack.heightAnchor.constraint(equalToConstant: 50.0),
scrollView.topAnchor.constraint(equalTo: btnStack.bottomAnchor, constant: 0.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
contentStackView.topAnchor.constraint(equalTo: cg.topAnchor, constant: 0.0),
contentStackView.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 0.0),
contentStackView.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: 0.0),
contentStackView.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: 0.0),
contentStackView.heightAnchor.constraint(equalTo: fg.heightAnchor),
])
// create 5 "Day" buttons and add them to the btnStack view
for i in 1...5 {
let b = UIButton()
b.setTitle("Day \(i)", for: [])
b.setTitleColor(.darkGray, for: .normal)
b.setTitleColor(.lightGray, for: .highlighted)
b.titleLabel?.font = .systemFont(ofSize: 14.0, weight: .bold)
b.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
btnStack.addArrangedSubview(b)
}
// create 5 views for the scroll view
let colors: [UIColor] = [
.yellow, .green, .systemBlue, .cyan, .systemYellow,
]
for (i, c) in colors.enumerated() {
let v = UILabel()
v.font = .systemFont(ofSize: 80.0, weight: .regular)
v.text = "\(i + 1)"
v.textAlignment = .center
v.backgroundColor = c
contentStackView.addArrangedSubview(v)
v.widthAnchor.constraint(equalTo: fg.widthAnchor).isActive = true
}
// movingSelectorView will partially cover the "Day" buttons frames, so
// don't let it capture touches
movingSelectorView.isUserInteractionEnabled = false
movingSelectorView.backgroundColor = .darkGray
view.addSubview(movingSelectorView)
scrollView.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let b = btnStack.arrangedSubviews.first {
// set the frame size and initial position of the movingSelectorView
movingSelectorView.frame = .init(x: 0.0, y: btnStack.frame.maxY - 12.0, width: 20.0, height: 3.0)
// set btnCenterOffset to one-half the width of a "Day" button
btnCenterOffset = b.frame.width * 0.5
// move the selector view to the center of the first "Day" button
movingSelectorView.center.x = btnCenterOffset + btnStack.frame.origin.x
}
}
@objc func btnTapped(_ sender: UIButton) {
guard let idx = btnStack.arrangedSubviews.firstIndex(of: sender) else { return }
// "Day" button was tapped, so scroll the scroll view to the associated view
let w: CGFloat = scrollView.frame.width
let h: CGFloat = scrollView.frame.height
let r: CGRect = .init(x: CGFloat(idx) * w, y: 0, width: w, height: h)
scrollView.scrollRectToVisible(r, animated: true)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// get the percentage that the scroll view has been scrolled
// based on its total contentSize.width
let pct = scrollView.contentOffset.x / scrollView.contentSize.width
// move the selector view based on that percentage
movingSelectorView.center.x = btnStack.frame.width * pct + btnCenterOffset + btnStack.frame.origin.x
}
}
and how it looks when running:
This will work - with Zero code changes - independent of device / view size:
Note: This is EXAMPLE CODE ONLY!!! -- it is meant to help you get started, and is not intended to be "Production Ready"