SwiftUI LazyVGrid animating change of "page" - ios

I’m trying to treat an entire LazyVGrid as pages, and animate when switching forward or back. A simple slide animation would be fine.
I currently have something along the lines of
LazyVGrid(
columns: [array of column info],
alignment: .center,
spacing: 4
) {
ForEach(cells) { cell in
CellView(cell: cell)
}
}
I have pages of cells that get set on the state when you hit the fwd or back buttons, and they are immediately re-rendered. However, it would be cool to set a forward/back slide when the buttons are hit. I haven’t been able to figure this out easily with transition and animation.
If there’s an entirely different library doing this, I’d be open to trying it.
Thanks so much!

Related

Get frame/dimensions from SwiftUI view(s)

Let's assume, to present a specific piece of information I have two different kinds of custom SwiftUI views. For the sake of an example my data is two Strings and the options to display them is either in a
HStack { Text() Spacer() Text() }
or
VStack {
Text()
Text()
}
style. In order to select the best fitting one, I would need to render them and make a choice based on resulting dimensions. For instance, if one of the text views in the first style would approach 50% of the window size, I would rather go with the second style.
How would I go about this without the user seeing the temporary views?
I know about GeometryReader, but I don't know how I could render my "candidate views" off screen, determine sizes and then make a selection for my actual view hierarchy.
Any hints?
I don't know how to work properly with SwiftUI. However, the following is an approach for what you want using UIKit and Labels for text:
let max_width = UIScreen.main.bounds.width/2
if label.intrinsicContentSize.width > max_width{
//The text would occupy more than 50% of the screen in width
}else{
//The text won't occupy more than 50% of the screen in width
}
You would have to run the previous function on each Label and there you can decide.
I know this is not a complete answer because you are working with SwiftUI, but someone may know how to apply the previous code on there.

SwiftUI Xcode 12.5 with very basic NavigationView layout Issues

I have been developing iOS for about a decade and every time I try to take the dive into SwiftUI I spend more time than ever wrestling what should seemingly be a simple task. While working on an app with very simple navigation setup I kept seeing two errors in the console: Unable to present. Please file a bug. and Unbalanced calls to begin/end appearance transitions for <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVVS_22_VariadicView_Children7ElementGVS_18StyleContextWriterVS_19SidebarStyleContext___: 0x7fd913d0bd90>. The first happens when there are at least 3 Views to navigate from (I don't understand why this is a significant threshold and my end goal uses a LazyVGrid with a ForEach) with navigation links and the second error happens on rotating to landscape and then back to portrait. I believed this to be related to how the phone is presenting the sidebar but even changing to StackNavigationViewStyle produced similar problems.
If this were a UIKit application I can absolutely solve for the Unbalanced calls situation but SwiftUI really takes away some of the lower level capabilities that I am used to having control of when it comes to building Views and navigation stacks.
I finally attempted to just start a new project from scratch and place the minimal amount of code in the ContentView:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("Number 1")) {
Text("Number 1")
}
NavigationLink(destination: Text("Number 2")) {
Text("Number 2")
}
NavigationLink(destination: Text("Number 3")) {
Text("Number 3")
}
}
}
}
}
Running the above code produced the issues on rotation. Another issue that I came across was when you press the < back button after these issues start, the navigation stops working completely and the "detail" view never gets updated.
It is clear that the direction is SwiftUI so I am trying to really go this direction (maybe next week will have some amazing improvements) but this seems to be a pretty significant issue on a very simple set of code. I am hoping that I am just doing something wrong that someone can point out quickly.
EDITED:
After more exploration I found that the second error happens on the 11, 11 Pro Max, and 12 Pro Max where the navigation stack changes to the sidebar by default. Changing the style to StackNavigationViewStyle does eliminate the second error (but doesn't help if I do want to use the sidebar style) but the first error remains. To Schottky's point, changing VStack to List will also eliminate the first error. However a newer convention (based on WWDC videos at least) to solve for collection views I believe is to use a LazyV/HGrid with a ForEach within it which is actually what I am attempting to accomplish. I didn't put that as my code here since I wanted the be able to reproduce the error in the simplest form of course to ensure it wasn't something buried in my view hierarchy.
The way I was able to accomplish this was via a post I found at HackingWithSwift here. I still had to use .navigationViewStyle(StackNavigationViewStyle()) to eliminate a different error as it seems that the initial iterations of SwiftUI focuses on the 80% of cases where multiple NavigationLinks only really exist in the List View type. I have not tested this with the latest beta but if you try to use the other navigation style (Column) it will throw another error: unbalanced calls to begin/end appearance transactions. I am okay with this for now as this at least allows me to create a screen that does not have line separators and navigation arrows....I just don't have the Master-Detail setup by default.

How to prevent automatic scrolling of UICollectionView by VoiceOver?

I have a horizontally scrolled UICollectionView with a title label above it and a UIPageControl below it.
UILabel
UICollectionView
UIPageControl
When I turn on the VoiceOver accessibility feature and start traversing the screen sequentially, the collection view scrolls to the beginning or end automatically. Making the page jump off suddenly. For example, if I scroll to the 2nd page using page control, and move back to the collection view, it shows and reads the last page unexpectedly. Since I'm using the page control for navigation in the accessibility mode, I'd like to prevent the automatic scrolling.
How do I prevent or counter that?
I found an issue that seems to describe the same problem, but there's no workaround suggestion: iOS 8.4: Scroll view resets contentOffset with Voice Over enabled shortly after view appear
I encountered it on iOS 13.4.1 iPhone 11 Pro
UIScrollViewDelegate.scrollViewDidScroll(_:)
A change in the accessibility focus that triggers an automatic scrolling also triggers a call to scrollViewDidScroll(_:) in your UIScrollViewDelegate. Use that to counter the automatic scrolling effect, f.i. by setting contentOffset the way you prefer it.
You may need to detect that the scrolling was actually triggered by accessibility features, and not the user dragging or pinching. UIAccessibility.isVoiceOverRunning and UIAccessibilityFocus.accessibilityElementDidBecomeFocused() are your friends here. Beware that changing contentOffset (or zoomScale or whatever is needed) may trigger another call to scrollViewDidScroll(_:), so you need to prevent an infinite recursion.
Using #pommy's suggestions, I was able to fix my similar issue. In the code I was working on, the most appropriate place to make the change ended up being CalendarCollectionView.setContentOffset(_:animated:), where CalendarCollectionView is a UICollectionView subclass. Specifically, it's a JTACMonthView subclass, but that should not be of any relevance to this answer.
From the name, you can see my use case: a calendar which shows a month at a time. It could have many months both into the future and the past, but the usual user focus is likely to start somewhere in the middle.
Like the OP, I found that swiping from an outer element to the collection view with VoiceOver enabled caused the focus to go to the first date in the calendar, in my case 1st January 1951 (Date.farPast, I believe.) An interesting aside: Switch Control navigation did not cause the same behaviour.
The underlying behaviour was that contentOffset was getting set to 0.0 in the dimension that the collection view scrolls. In my code, that direction is held in style, and changes based on configuration, but in most applications it's likely to be fixed.
My code simply blocks any offset changes to 0.0 when VoiceOver is enabled. This is pretty naïve, and won't be suitable for all apps, but gives a concrete example which I hope will help some others!
override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
if shouldPreventAccessibilityFocusScrollback(for: contentOffset) {
return
}
super.setContentOffset(contentOffset, animated: animated)
}
func shouldPreventAccessibilityFocusScrollback(for newContentOffset: CGPoint) -> Bool {
if UIAccessibility.isVoiceOverRunning {
switch style {
case .horizontal:
return newContentOffset.x == 0
case .vertical:
return newContentOffset.y == 0
}
}
return false
}
I spent quite a long time trying to determine when UIAccessibilityFocus moved from something outside the collection view, to something inside the collection view, which is ideally the only time we want to block these automatic scrolls. I was unsuccessful, but I think that was mostly due to subclassing a third party collection view (the calendar). There's definitely more merit to that approach, if you can get it to work... but it will require some careful management of state.

Swift UI List: Open external URL when cell is tapped

I am trying to open a URL when one of the List's cells is tapped. I tried adding the modifier onTapGesture to the cell itself and then calling UIApplication.shared.open(url), but this only works if the tap is right on the cell view's elements (and not on the cell's background).
I also tried to add a background view (Rectangle) to the cell with opacity 0.01, but although this works the Rectangle is quite visible despite its low opacity.
Is there any workaround to make the whole row tappable?
Found a solution, here it is in case it helps anyone in the future:
// edit:
Used a single Button, with the required action (i.e. openURL in my case) in the action closure, and with my custom view returned in the label closure.
#stakri, What you're looking for is ".contentShape()"
Rectangle()
.stroke()
.onTapGesture() {
UIApplication.shared.open(URL(string: "https://stackoverflow.com")!)
}
above code will only work if you tap on the 'stroke' outline of the rectangle, but .contentShape() will make the entire area tappable without the need to nest it inside of a Stack:
Rectangle()
.stroke()
.contentShape( Rectangle() )
.onTapGesture() {
UIApplication.shared.open(URL(string: "https://stackoverflow.com")!)
}
Others coming to this question will likely be looking for how to specifically open a URL, you'll notice I force unwrapped my URL. Open to feedback on that, but barring any issues brought up, this works well.

swipe between views using scrollview - swift

in viewDidLoad :
viewPager.dataSource = self
--
extension DetailsViewController:ViewPagerDataSource{
func numberOfItems(_ viewPager:ViewPager) -> Int {
return dict!.count
}
func viewAtIndex(_ viewPager:ViewPager, index:Int, view:UIView?) -> UIView {
func viewAtIndex(_ viewPager:ViewPager, index:Int, view:UIView?) -> UIView {
return view
the result is slow , it does not show the animation of transition maybe cuz I return the scrollview view not normal view , and when I swipe to left it shows nothing (swipe backwards) , is there anything better to implement ?
thanks for help.
Your question isn't very clear, and then you post a BUNCH of code, including code that's irrelevant to your question. You should only post the parts that directly relate to your question.
It looks like what you want is a UIPageViewController. That manages an array of view controllers, each of which holds the contents of one page. You can either user it with a page curl animation like iBooks, or a scrolling mode. You want the scrolling mode.
If you search in the Xcode help (or online, it looks like the link to download the app isn't in Xcode 8) on "PhotoScroller" You'll find a demo app that illustrates how it works.
That app is written in Objective-C, but it will at least show you the UI it offers.
In order to use a UIPageViewController you'll have to restructure your detail view controller to manage an array of child view controllers instead of only using a single view controller.
If you want side scrolling AND up and down scrolling among a grid of tiles then you want a collection view instead, but that's more work to set up.

Resources