SwiftUI: Disable ScrollView scrolling to top if top of screen is touched - swift

I have a UI which resides in a VStack{} with a custom topbar and a ScrollView. I have buttons on my top bar which do things like open user account details. I find that if I press some of these buttons the ScrollView autoscrolls to the top of the screen. Is there a way to stop that?
My code:
struct MyView: View {
var body: some View {
VStack(spacing:0) {
TopBarView()
ScrollView(.vertical) {
ForEach(0..<100, id: \.self) { index in
Text(String(index))
}
}
}
}
struct TopBarView: View {
var body: some View {
Text("This is a top bar")
}
}
}
If I touch the top of the screen above the scrollView's frame, the scrollView scrolls all the way to the top. Is there a way to disable that behavior?

This is not a SwiftUI or Xcode or ScrollView problem.
This is a built-in feature of iPhone.
When you tap the top-center edge of the screen, displayed contents will be scrolled up.
Try opening apps like Facebook, WhatsApp, then scroll and tap at the same position, you will see the displayed contents scroll up.

For UIScrollView, there's a scrollsToTop property you can disable:
let scrollview = UIScrollView()
scrollview.scrollsToTop = false
Nothing for SwiftUI's ScrollView as far as I can tell. You could use UIViewRepresentable to wrap UIScrollView for SwiftUI.

Related

How to make mouse scroll to scroll horizontally in swiftui?

I have a View built using SwiftUI that uses Scroll view horizontally.
var body: some View {
VStack {
// this should scroll horizontally when user uses the mouse scroller
HStack {
}
}
}
Currently user can do that by pressing shift button and scroll but I want to get rid of shift button
Goal: Mouse scroller should scroll horizontally without using shift button
You can see a tutorial here:
https://www.youtube.com/watch?v=-JjN2sRQXLc&t=1176s
With the final code here:
https://github.com/gahntpo/ItunesSearchApp/blob/main/ItunesSearchApp/view/album/SongsForAlbumListView.swift
In sum you will need a ScrollViewReader that gives you a closure with a proxy that could be called to scrollTo method.
You'll need to mart the destinations to scrollTo with an identifiable object or using .id modifier.
All explained in the video.

Pass through clicks/taps to underlying UIViewRepresentable

I have a UIViewRepresentable that I am using the blend a project created with UIViews and SwiftUI.
My views are full screen and my issue is that I cannot find a way to pass touches through the transparent UINavigationBar at the top of the screen to my underlying UIView. That means that the top 100 or so pixels don't respond to touch even though they're visible. I have seen this post and solution, but it does not work in my case. I believe it's because I'm using UIViewRepresentable.
For my content view I have:
struct ContentView: View {
let values: [String] = ViewType.allCases.map { "\($0.rawValue)" }
var body: some View {
NavigationView {
List {
ForEach(values, id: \.self) { value in
NavigationLink(value, destination: CustomView(viewName: value).ignoresSafeArea())
}
.navigationTitle("Nav Title")
}
}
.accentColor(.white)
}
}
struct CustomView: UIViewRepresentable {
let viewType: String
func makeUIView(context: Context) -> CustomView {
CustomView(view: viewType)
}
func updateUIView(_ view: CustomView, context: Context) {
view.draw(view.frame)
}
}
Here is an image of my view hierarchy that shows the UINavigationBar blocking the top 100 or so pixels of my underlying UIView and preventing touches from being registered by the view.
I know I can disable the navigation bar and that does work, but I do need the back button. Ideally, I'd like to understand how I can apply the solution I linked to above which will pass touches unless there is a UIControl in the way.
UPDATE
I tried Asperi's suggestion of using transparency and then disabling the navigation bar and it occurred to me to check the view debugger with it disabled.
It turns out that the back button is still there in the view hierarchy, but it is not visible in the app when the nav bar is disabled. If there were a way to keep the nav bar disabled but enable the button, that would be the ideal situation.

ScrollView nested in TabView reset position to start while changing tab

I'm experimenting with SwiftUI and I found a weird behaviour of ScrollView when nested in a TabView. If I swipe the ScrollView down and then do a slow gesture to swipe to the next tab, it sometimes reset the scrollView Position to the start. I'm not sure how to explain it well so here is a gif:
I'm trying to understand what cause this and if there is a way to avoid it.
Here is the code I use:
var body: some View {
TabView(selection: $selectedTab,
content: {
ForEach(coreData.pageList, id: \.self) { page in
if(coreData.pageList[page] == nil) {
ProgressView().onAppear(perform: {
loadPage(page: page)
})
} else {
ScrollView {
Text("START OF PAGE ------- -----Page Number: \(page)! This is a very short text made big to simulate scrolling. This is a very short text made big to simulate scrolling. ").font(.system(size: 90))
}
}
}
})
}
Am I missing something simple, is it a bug (with workaround), am I using these views wrong?
Ultimately I just want to have something that look like a page that you swipe right left or right but having the scroll position reset so fast would not be great user experience and doesn't look great
Edit: I haven't found any solution yet, but an interesting point: if I put the ProgressView inside the scrollView the scrollview does NOT reset position but the next tab will already be scrolled with the same amount as the previews page. This is equally "bad" if not worse :-(.
Try to put it into separated view (passing all needed parameters), so SwiftUI will see that view not changed and so not rerender it, ie. it should look like
if(coreData.pageList[page] == nil) {
ProgressView().onAppear(perform: {
loadPage(page: page)
})
} else {
DetailsView(page: page) // << ScrollView inside
}

Why does an SwiftUI horizontal ScrollView inside of a vertical ScrollView refuse to ignore the insets on Landscape mode?

I start with the following horizontal ScrollView in SwiftUI and, testing it on an iPhone 11 in Landscape mode, I see that even though the ScrollView's frame respect both .leading and .trailing insets, when scrolling through the elements inside, these do show up behind the notch and outside the limits of the ScrollView.
ScrollView(.horizontal) {
HStack {
ForEach(1..<20) { index in
Rectangle().frame(width: 100, height: 100).opacity(0.5)
}
}
}.background(Color(.red)).opacity(1)
This is desired behaviour, and I see it happening in apps like Shortcuts, or photos for iOS 13. However, when I embed the above into a Vertical ScrollView and test in the same conditions:
ScrollView(.vertical) {
VStack {
ScrollView(.horizontal) {
HStack {
ForEach(1..<20) { index in
Rectangle().frame(width: 100, height: 100).opacity(0.5)
}
}
}.background(Color(.red)).opacity(1)
}
}.background(Color(.blue)).opacity(0.5)
In this case the elements inside the horizontal ScrollView never go outside the parent ScrollView. If I try .edgesIgnoringSafeArea(.all) on the parent ScrollView and I start the app on Landscape, after the first interaction there seems to be some repositioning, but then the horizontal ScrollView shows up clipped when rotating to Portrait mode.
Why is this happening and how can I achieve the desired behaviour? (E.G.: elements of the Horizontal ScrollView showing up outside of their ScrollView's limits when scrolling, but everything else remaining respectful of the SafeArea)

Buttons outside of UIScrollView are not touchable

I'm facing an annoying problem with UIScrollView that my buttons cannot be touched if they are outside of the scroll view but I dont know how to fix it now
I have tried some ways but no helps so far
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
var contentRect = CGRect.zero
for view in scrollContentView.subviews {
contentRect = contentRect.union(view.frame)
}
for view in scrollContentView.subviews {
contentRect = contentRect.union(view.frame)
}
scrollView.contentSize.height = contentRect.size.height
}
The code above just helps to make the scroll view scrollable
I also attached my sample project in this link
https://drive.google.com/open?id=19U8jecDNQbAnTFbG36KMRxHfaLLcaLDq
I strongly appreciate your advices. Thank you
You have not described your view hierarchy correctly. What you actually have is this:
Scroll view
Content view
Stack view
Buttons
The content view is what is causing the problem. Its height is pinned to the height of the view controller's main view — which is the height of the screen. But of course the stack view with its buttons is taller than the screen, in order to give you something to scroll to. So the lower part of the stack view, and the buttons at the bottom of the stack view, are below the bottom of the content view. Thus they are outside their superview. Thus they are untouchable. A view outside its superview (or its superview, or its superview, all the way up the view hierarchy) is untouchable.