Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
SwiftUI List overlay clipping
// NOTE: behavior is exhibited in the 14.1 simulator and previews in Xcode 12.1
struct OverlayRowClipping: View {
let rowData = (1...50).map { "row \($0)" }
@State var alertIsOpen = false
var body: some View {
List {
Section(header: Text("The Header")) {
ForEach(rowData, id: \.self) { item in
HStack {
Button(action: { alertIsOpen = true }) {
HStack {
Text(item)
Spacer()
}
// required to make the entire row respond to touch
.background(Color.white)
}
.border(Color.black)
.buttonStyle(PlainButtonStyle())
HelpButton()
.border(Color.black)
}
}
}
}
.alert(isPresented: $alertIsOpen) {
.init(title: Text("Main row button was tapped"))
}
.listStyle(GroupedListStyle())
.navigationBarTitle("Overlay Clipping", displayMode: .inline)
}
}
struct HelpButton: View {
@State var isOpen = false
var body: some View {
Button("Help") {
withAnimation { isOpen.toggle() }
}
// With the default (or no) button style the overlay is not clipped
// but touch handling is incorrect. If you tap on the left side of the
// Help button (roughly the H) the main row button responds to the touch.
// Also, the row background is highlighted when the button is tapped
// which is not desired in my use case. Further, I noticed that the
// highlighting behavior happens for **all** buttons in a row, including
// buttons with PlainButtonStyle, whenever **any** button in the row has
// the default style. This kind of nonlocal behavior seems problematic.
//
// When switching to PlainButtonStyle, touch handling and row highlighting
// behaves as desired, but the overlay is clipped in different ways
// depending on which help button is tapped. When the overlay
// is scrolled offscreen and back onscreen the redraw is sometimes different
// than the original draw. Scrolling a row off the top of the screen and
// back onscreen seems to "fix" the clipping the next time the overlay is
// displayed. Scrolling a row off the bottom of the screen and back on
// does the opposite, "breaking" a row where clipping was not happening.
.buttonStyle(PlainButtonStyle())
.overlay(overlay)
}
@ViewBuilder var overlay: some View {
if isOpen {
GeometryReader { geometry in
Color.red
.frame(width: 100, height: 100)
.offset(x: geometry.size.width - 100, y: -100)
}
} else {
EmptyView()
}
}
}
struct OverlayRowClipping_Previews: PreviewProvider {
static var previews: some View {
OverlayRowClipping()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.