How to disable vertical bounce in SwiftUI on a single ScrollView - modal-dialog

I have a half modal view coming from the bottom of the screen with a scrollView, and when there's isn't enough content to scroll I want the drag gesture on the internal scrollView to apply to the modal and expand it or collapse it.
I tried using:
init() {
UIScrollView.appearance().bounces = false
}
And it works fine but this disables the bouncing effect on all the scrollViews in my app.
Is there a way to apply this for a single ScrollView or at least a single View?

you can add this to a ViewModifier:
struct SomeModifier: ViewModifier {
init() {
UIScrollView.appearance().bounces = false
}
func body(content: Content) -> some View {
return content
}
}
ScrollView {
Text("Some scroll view")
}.modifier(SomeModifier()

Related

Vertical ScrollView disabled when cursor is inside horizontal ScrollView in SwiftUI

My SwiftUI app has a List of ScrollView(.horizontal). I would like to vertical scroll to go through each of the horizontal scroll views (Rows) and to horizontal scroll within the Row. The following code accomplishes this :
struct Row: View {
var body: some View {
ScrollView(.horizontal) {
HStack {
ForEach(0..<100) { i in
Text(String(i))
.padding(5)
}
}
.padding(10)
.background(.red)
}
}
}
struct ContentView: View {
var body: some View {
List(0..<100) {_ in
Row()
}
.background(.blue)
}
}
The issue is, if my cursor is inside the bounds of a Row, the outer vertical scroll is disabled. How can I "promote" the vertical scroll gesture of the nested Row to the List it lives in?
The reason I want this behavior is so that if the user is scrolling through the List, I don't want them to reposition their cursor if it lands inside the bounds of a Row.
Vertical Scroll: GIF
Horizontal Scroll: GIF
Issue
If you exchange the List with another vertical ScrollView it works both ways (don't ask me why).
struct ContentView: View {
var body: some View {
ScrollView(.vertical) {
ForEach(0..<100) { row in
Row()
}
}
.background(.blue)
}
}

Only show horizontal scroll indicator on hover

I'm developing a macOS application. I want to hide the horizontal scroll indicators when the mouse is not hovering over the view.
I got it working with the .onHover modifier. The problem is that the scroll indicator takes up space, so when it appears the view gets bigger and everything is moved below it. Is there a better way to do this, or at least a way to not change the views height when it appears?
Here is some stripped down code:
struct Test: View {
#State private var mouseHover = false
var body: some View {
ScrollView(.horizontal, showsIndicators: mouseHover) {
RowOfItems()
.padding(.bottom)
}
.onHover { hover in
mouseHover = hover
}
}
}

SwiftUI ScrollView not properly setting content offset when nested in UIViewRepresentable

I'm trying to introspect the UIScrollView under the hood of a SwiftUI ScrollView and that's all fine and dandy.
But I noticed that there were some issues with spacing and animation when scrolling going the pure representable approach so instead I'm trying to leverage a few properties of the underlying UIScrollView via a few bindings that are updates as part of the scrollview delegate.
That's not the main concern, the main issue I'm having even if I'm not doing any bindings at all but a bare bones approach of using the ScrollView in a UIViewRepresentable context is that it behaves differently.
public struct PerfectScrollView<Content: View>: UIViewRepresentable {
private let content: Content
public init(#ViewBuilder content: () -> Content) {
self.content = content()
}
public func updateUIView(_ uiView: UIViewType, context: Context) {
}
public func makeUIView(context: Context) -> UIView {
UIHostingController(rootView:
ScrollView {
content
}
).view
}
}
// Usage
PerfectScrollView {
ForEach(0..<100) { Text("Hello \($0)") }
}
// vs
ScrollView {
ForEach(0..<100) { Text("Hello \($0)") }
}
The result of PerfectScrollView renders this where I'm centered in the middle of the scrollview's content.
But the normal ScrollView (not setup via UIViewRepresentable protocol) renders an appropriate scrollview.
Any ideas about what actually is happening? From interacting with he PerfectScrollView it's like I've reached the end/bounds of the view and trying to scroll results in the rubber band style resistance animation scrolling up or down.
Any help/feedback would be greatly appreciated :)
The solution was to use UIViewControllerRepresentable protocol since i was using ScrollView in a UIHostingController context.

ZStack explicit animation not working when conditionally hiding/showing a view

I want to create some sort of banner view that either moves in or out with animations when some state changes in SwiftUI. It feels like a basic task to do but the transition when showing the banner is not being animated. Hiding works fine.
So for demonstration purposes, here is a basic view modifier that creates a ZStack with the content and a conditional view that acts as the banner overlay and has a transition:
struct BannerViewModifier: ViewModifier {
let showBanner: Bool
func body(content: Content) -> some View {
ZStack {
content
if showBanner {
VStack {
Text("banner")
Spacer()
}.transition(.move(edge: .top).combined(with: .opacity))
}
}
}
}
If I would then go ahead and tack that view modifier onto a button that toggles a boolean like this (with explicit animations)...
#State var showBanner = false
var body: some View {
Button(showBanner ? "Hide banner" : "Show banner") {
withAnimation { showBanner.toggle() }
}.modifier(BannerViewModifier(showBanner: showBanner))
}
... it results in not animating the show transition as I already mentioned.
Solutions I've tried already:
When adding an implicit animation to the VStack with the transition modifier like this:
.animation(.default).transition(.move(edge: .top).combined(with: .opacity)) the show animation works.
However this feels like bad practice since the user of the view modifier cannot control the animation properties since they are baked into the view modifier already. Furthermore the animation modifier .animation(.default) was deprecated in iOS 15 and its replacement .animation(.default, value: showBanner) also does not animate the show transition either.

How to disable vertical scroll in TabView with SwiftUI?

I have set up a TabView in my application, so that I can swipe horizontally between multiple pages, but I also have an unwanted vertical scroll that may appear, with a bounce effect so. How can I disable this vertical scroll?
My code:
struct ContentView: View {
#State private var currentTabIndex: Double = 0
var body: some View {
VStack {
TabView(selection: $currentTabIndex) {
Text("Text n°1")
.tag(0)
Text("Text n°2")
.tag(1)
}
.border(Color.black)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
}
I had this same problem. It's not an exact solution, but you can turn off bouncing on scrollviews (which is used within a TabView). And as long as the items within the TabView are not larger than the TabView frame, it should act as if you disabled vertical scrolling.
I would call it either .onAppear or in your init function:
.onAppear(perform: {
UIScrollView.appearance().bounces = false
})
Note: this disables the bouncing on ALL scrollviews across your app... So you may want to re-enable it .onDisappear.
Still an issue with Xcode 12.4.
I managed to workaround that by wrapping the TabView within a ScrollView and using the alwaysBounceVertical property set to false, as follow:
ScrollView(.horizontal) {
TabView {
///your content goes here
}
.tabViewStyle(PageTabViewStyle())
}
.onAppear(perform: {
UIScrollView.appearance().alwaysBounceVertical = false
})
.onDisappear(perform: {
UIScrollView.appearance().alwaysBounceVertical = true
})
I actually came across this because I saw this effect in a tutorial but couldn’t replicate it on iOS 15.2. However, I managed to replicate it on iOS 14.4 on another simulator side by side. So I guess this behaviour is disabled or fundamentally changed in the newer iOS.
Demonstration