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