How to make mouse scroll to scroll horizontally in swiftui? - swift

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.

Related

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

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.

Scroll to HTML element ID in VStack of WKWebView views

I have a ScrollView with a LazyVStack outputting items in ForEach.
I'm rendering HTML in the LazyVStack with the RichText library: https://github.com/NuPlay/RichText which is essentially a dynamically sized WKWebView for each item in the VStack.
#State var scrollTarget: Int?
//struct view
ScrollView {
ScrollViewReader { proxy in
LazyVStack {
ForEach(ficDetail.chapters.indices, id: \.self ) { i in
Text(self.ficDetail.chapters[i].post_title)
.id(self.ficDetail.chapters[i].id)
RichText(html: self.ficDetail.chapters[i].html)
.lineHeight(170)
.colorScheme(ColorScheme.auto)
.imageRadius(6)
}
}
.onChange(of: scrollTarget) { newValue in
if let target = newValue {
scrollTarget = nil // allows for future triggers
proxy.scrollTo(target)
}
}
}
}
As the above code shows, I'm using ScrollViewReader to auto scroll the ScrollView to the Text view with property: .id(self.ficDetail.chapters[i].id).
The issue is that the HTML I'm displaying inside each RichText view is very large, and I need to let users auto scroll to specific areas inside each RichText HTML view, particularly if they close the app and I want to restore the Scene.
The rendered HTML has <span id="{Int}"> ... </span> wrapping each sentence inside the HTML.
Is there anyway to bind proxy.scrollTo to the embedded <span> IDs to scroll the LazyVStack to that <span id="{Int}">?
If not, is there another method already in practice for this use case?
When I webView.evaluateJavaScript("document.getElementById('30').scrollIntoView();" this only scrolls the inner WKWebview and not the outer ScrollView, so the HTML content inside the view is getting cutoff as it's scrolling out of view.
I'm considering recording the scroll offset of the ScrollView and finding a way to restore the Scene and scroll back to that offset. Though seems messy as the HTML content + CSS can change the offset sizes.
Another option I considered is adding HiddenView slices with .id()s proportional to the size of each RichText view to act as a proxy to the WKWebViews, but that also seems hacky.

How to implement PencilKit in SwiftUI with background and zoom?

I am trying to build a view in SwiftUI that allows me to draw on top of another view, for example a table, using PencilKit. Everything must be zoomable and the elements of the background view must be clickable.
I thought about using a ZStack:
ZStack {
CanvasView(canvasView: $canvasView)
BackgroundView()
}
In this way I can draw on top of the BackgroundView and interact with its elements, but how can I manage the zoom? When I zoom only the canvas scales itself.
I thought about using scrollViewDidZoom:
extension Coordinator: PKCanvasViewDelegate {
func scrollViewDidZoom(_ scrollView: UIScrollView) {
onZoom()
}
}
To know when the Canvas is zooming. Then the SwiftUI View change as below:
ZStack {
CanvasView(canvasView: $canvasView, onZoom: onZoom)
BackgroundView()
.scaleEffect(scale)
}
func onZoom() {
scale = canvasView.zoomScale
}
This way the BackgroundView scales itself but only focusing on the center.
Is there a way to follow not only the canvas zoom scale but also the anchor of the zoom?
Or more generally, is there a way to get what I want?
I see many apps that permits to draw on top of some view and interact with buttons inside that view, but I cannot find any useful documentation about that.
Thank you

How to show animation when open new Window in SwiftUI

I have Bottom sheet I want to show it above TabBar so user tab on button inside List and it show options as Bottom sheet view
1st. I used SwiftUIX with OverlayWindow
like this
.windowOverlay(isKeyAndVisible: self.$optionsShown, {
GeometryReader { _ in
BottomSheetView(
isOpen: $optionsShown
) {
if optionsShown {
OptionsView()
}
}
.edgesIgnoringSafeArea(.all)
}
})
it working but without animation !
2nd. Then I read this Post
I tried it, it working fine with animation if I create Window inside SceneDelegate
but If I create Window outside SceneDelegate it will work without animation like SwiftUIX
Inside SceneDelegate :
Demo Project
Outside SceneDelegate :
Demo Project
I need to create new window inside my view because I will need to pass some values, and I used bottom sheet on multiple view with different views
but I cannot figure out why the animation won't work outside SceneDelegate

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
}