I have a UIKit app with a custom navigation controller. I want my view title to go up into the navigation bar when the user scrolls down the screen. It looks like UIScrollEdgeElementContainerInteraction should do what I want, but I am having trouble using it.
Below is a sample, where a header view represents a title. I added the interaction to the header view, but it seems to have no effect. Am I missing a step? Perhaps I misunderstand what this is supposed to do, or perhaps I do not understand the preconditions to make this work.
I am hoping someone can tell me what I am doing wrong, or point me to some working sample code.
Thank you.
John
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var headerView: UIVisualEffectView!
var tableView: UITableView!
var interaction: UIScrollEdgeElementContainerInteraction!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView = UITableView()
self.tableView.translatesAutoresizingMaskIntoConstraints = false
self.tableView.topEdgeEffect.style = .soft
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(self.tableView)
self.view.addConstraints([
self.tableView.topAnchor.constraint(equalTo: self.view.topAnchor),
self.tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
self.tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
self.tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
])
self.headerView = UIVisualEffectView(effect: UIGlassEffect(style: .regular))
self.headerView.translatesAutoresizingMaskIntoConstraints = false
self.headerView.backgroundColor = .green
self.view.addSubview(self.headerView)
self.view.addConstraints([
self.headerView.topAnchor.constraint(equalTo: self.view.topAnchor),
self.headerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
self.headerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
self.headerView.heightAnchor.constraint(equalToConstant: 100.0),
])
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "my text"
self.headerView.contentView.addSubview(label)
self.headerView.contentView.addConstraints([
label.centerXAnchor.constraint(equalTo: self.headerView.contentView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: self.headerView.contentView.centerYAnchor),
])
self.interaction = UIScrollEdgeElementContainerInteraction()
self.interaction.scrollView = self.tableView
self.interaction.edge = .top
self.headerView.addInteraction(self.interaction)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "row \(indexPath.row + 1)"
return cell
}
}
Did you try with a physical device? self.interaction.edge = .top
doesn't work for me either on an iOS 26 Beta 4 simulator. (.bottom
does work.)
The following code works for me on my iPhone 16 Plus + iOS 26 (Beta 4):
/**
Note: Use a physical iPhone to test this code. The top edge effect doesn't work on an iOS simulator at the time of writing (Beta 4).
*/
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var text: String = ""
for _ in 0..<1000 {
text += "Hello, World, Hello, World, Hello, World, Hello, World, Hello, World, Hello, World!\n"
}
let scrollView = UITextView()
scrollView.text = text
scrollView.frame = view.bounds
scrollView.contentSize = CGSize(width: view.bounds.width, height: view.bounds.height * 2)
self.view.addSubview(scrollView)
// TEST1: Top edge.
var containerView = UIView()
var bounds = view.bounds
containerView.frame = CGRect(
x: 0, y: 20, width: bounds.width, height: bounds.height / 3
).insetBy(dx: 50, dy: 50)
var interaction = UIScrollEdgeElementContainerInteraction()
interaction.scrollView = scrollView
interaction.edge = .top
containerView.addInteraction(interaction)
let label = UILabel(frame: containerView.bounds.insetBy(dx: 50, dy: 50))
label.backgroundColor = .blue
containerView.addSubview(label)
view.addSubview(containerView)
// TEST2: Bottom edge.
containerView = UIView()
bounds = view.bounds
containerView.frame = CGRect(
x: 0, y: bounds.height - bounds.height / 3, width: bounds.width, height: bounds.height / 3
).insetBy(dx: 50, dy: 50)
interaction = UIScrollEdgeElementContainerInteraction()
interaction.scrollView = scrollView
interaction.edge = .bottom
containerView.addInteraction(interaction)
let button = UIButton(type: .system)
button.frame = containerView.bounds.insetBy(dx: 50, dy: 50)
button.setTitle("Tap Me", for: .normal)
button.configuration = .glass()
containerView.addSubview(button)
view.addSubview(containerView)
}
}
Best,
——
Ziqiao Chen
Worldwide Developer Relations.