UIScrollView inside custom tab bar ZStack not receiving horizontal swipe gestures in SwiftUI

1 day ago 1
ARTICLE AD BOX

I have a SwiftUI app with a custom tab bar (no TabView — using a ZStack + VStack with manual tab switching). On my Wardrobe tab, I have a horizontal UIScrollView wrapped in UIViewRepresentable for a category chip row. The scroll view receives no horizontal swipe gestures at all — completely frozen. Taps work fine. I've confirmed isScrollEnabled = true, alwaysBounceHorizontal = true, delaysContentTouches = false. The chip row sits inside a NavigationStack. What is preventing horizontal swipes from reaching the UIScrollView?

struct ChipScrollView: UIViewRepresentable { let categories: [String] let selectedCategory: String let onSelect: (String) -> Void func makeUIView(context: Context) -> UIScrollView { let scrollView = UIScrollView() scrollView.showsHorizontalScrollIndicator = false scrollView.showsVerticalScrollIndicator = false scrollView.alwaysBounceHorizontal = true scrollView.alwaysBounceVertical = false scrollView.backgroundColor = .clear scrollView.delaysContentTouches = false scrollView.canCancelContentTouches = true scrollView.isScrollEnabled = true let stack = UIStackView() stack.axis = .horizontal stack.spacing = 8 stack.alignment = .center stack.translatesAutoresizingMaskIntoConstraints = false stack.tag = 100 scrollView.addSubview(stack) NSLayoutConstraint.activate([ stack.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 16), stack.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -16), stack.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), stack.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), stack.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor) ]) buildChips(in: stack) return scrollView } func updateUIView(_ scrollView: UIScrollView, context: Context) { guard let stack = scrollView.viewWithTag(100) as? UIStackView else { return } buildChips(in: stack) } private func buildChips(in stack: UIStackView) { stack.arrangedSubviews.forEach { $0.removeFromSuperview() } for category in categories { let isSelected = category == selectedCategory let button = UIButton(type: .custom) var config = UIButton.Configuration.filled() config.title = category config.baseForegroundColor = isSelected ? UIColor(red: 0.06, green: 0.06, blue: 0.06, alpha: 1) : .label config.baseBackgroundColor = isSelected ? UIColor(red: 0.87, green: 0.82, blue: 0.71, alpha: 1) : .secondarySystemBackground config.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16) config.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { attrs in var a = attrs a.font = UIFont.systemFont(ofSize: 15, weight: isSelected ? .semibold : .regular) return a } button.configuration = config button.layer.cornerRadius = 17 button.clipsToBounds = true let cat = category button.addAction(UIAction { _ in onSelect(cat) }, for: .touchUpInside) stack.addArrangedSubview(button) } } func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator {} }
Read Entire Article