Does a LazyVStack remain "lazy" if it's inside a VStack? - swiftui

Let's consider a list of 100 posts. According to Apple, if I layout them inside a LazyVStack:
the stack view doesn’t create items until it needs to render them
onscreen.
What if I embed that LazyVStack inside a VStack? Does it still load the views "as needed"?

struct MyView: View {
init() {
print("init...")
}
var body: some View {
Text("test")
}
}
struct ContentView: View {
var body: some View {
ScrollView {
VStack {
LazyVStack {
ForEach.init(0..<100) { int in
MyView()
}
}
}
}
}
}
Running the above code, as we scroll we can see more MyViews are init'd (by viewing the print statements in the console), so it seems like a LazyVStack in a VStack does indeed create it's content lazily. We can see the same is true when removing the VStack as well.

Related

Navigation + Tabview + Sheet broken in iOS 15

It looks like Navigation + TabView + Sheet is broken in iOS 15.
When I do this:
ContentView -> DetailView -> Bottom Sheet
When the bottom sheet comes up, the Detail view is automatically popped off the stack:
https://www.youtube.com/watch?v=gguLptAx0l4
I expect the Detail view to stay there even when the bottom sheet appears. Does anyone have any idea on why this happens and how to fix it?
Here is my sample code:
import Combine
import SwiftUI
import RealmSwift
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
TabItemView(num: 1)
.tabItem {
Text("One")
}
TabItemView(num: 2)
.tabItem {
Text("Two")
}
}
}
}
}
struct TabItemView: View {
private let num: Int
init(num: Int) {
self.num = num
}
var body: some View {
NavigationLink(destination: DetailView(text: "Detail View \(num)")) {
Text("Go to Detail View")
}
}
}
struct DetailView: View {
#State private var showingSheet = false
private let text: String
init(text: String) {
self.text = text
}
var body: some View {
Button("Open Sheet") {
showingSheet.toggle()
}.sheet(isPresented: $showingSheet) {
Text("Sheet Text")
}
}
}
This works on iOS 14 btw
UPDATE 1:
Tried #Sebastian's suggestion of putting NavigationView inside of TabView. While this fixed the nav bug, it fundamentally changed the behavior (I don't want to show the tabs in DetailView).
Also tried his suggestion of using Introspect to set navigationController.hidesBottomBarWhenPushed = true on the NavigationLink destination, but that didn't do anything:
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
TabItemView(num: 1)
}.tabItem {
Text("One")
}
NavigationView {
TabItemView(num: 2)
}.tabItem {
Text("Two")
}
}
}
}
struct TabItemView: View {
private let num: Int
init(num: Int) {
self.num = num
}
var body: some View {
NavigationLink(destination: DetailView(text: "Detail View \(num)").introspectNavigationController { navigationController in
navigationController.hidesBottomBarWhenPushed = true
}) {
Text("Go to Detail View")
}
}
}
struct DetailView: View {
#State private var showingSheet = false
private let text: String
init(text: String) {
self.text = text
}
var body: some View {
Button("Open Sheet") {
showingSheet.toggle()
}.sheet(isPresented: $showingSheet) {
Text("Sheet Text")
}
}
}
You need to flip how you nest TabView & NavigationView. Instead of nesting several TabView views inside a NavigationView, use the TabView as the parent component, with a NavigationView for each tab.
This is how the updated ContentView would look like:
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
TabItemView(num: 1)
}
.tabItem {
Text("One")
}
NavigationView {
TabItemView(num: 2)
}
.tabItem {
Text("Two")
}
}
}
}
This makes sense and is more correct: The tabs should always be visible, but you want to show a different navigation stack with different content in each tab.
That it worked previously doesn't make it more correct - SwiftUI probably just changed its mind on dealing with unexpected situations. That, and the lack of error messages in these situations, is the downside of using a framework that tries to render anything you throw at it!
If the goal is specifically to hide the tabs when pushing a new view on a NavigationView (e.g., when tapping on a conversation in a messaging app), you have to use a different solution. Apple added the UIViewController.hidesBottomBarWhenPushed property to UIKit to support this specific use case.
This property is set on the UIViewController that, when presented, should not show a toolbar. In other words: Not the UINavigationController or the UITabBarController, but the child UIViewController that you push onto the UINavigationController.
This property is not supported in SwiftUI natively. You could set it using SwiftUI-Introspect, or simply write the navigation structure of your application using UIKit and write the views inside in SwiftUI, linking them using UIHostingViewController.

Avoid inherited styling inside List Section

When I add my custom widget into List's Section header, the elements inside my widget gets styled. Among others all the text become ALLCAPS. How can I avoid that styling, especially text capitalization?
struct MyHeader: View {
var body: some View {
Text("Hello wOrLd!")
}
}
struct ContentView: View {
var body: some View {
List {
Section(header: MyHeader()) {}
}
}
}
struct MyHeader: View {
var body: some View {
Text("Hello wOrLd!").textCase(.none)
}
}
Maybe it works

SwiftUI Navigation Multiple back button

When I push more than one view, multiple back buttons are visible in the navigation bar.
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination:SecView()) {
Text("Primo")
}
}
}
}
struct SecView: View {
var body: some View {
NavigationView {
NavigationLink(destination:TerView()) {
Text("Secondo")
}
}
}
}
struct TerView: View {
var body: some View {
Text("Hello World!")
}
}
I would like to have only one back button per view.
Here is a screenshot of the problem.
There should only be a single NavigationView at the root of your navigation stack.
Remove the NavigationView block from SecView and you will then have a single navigation bar owned by ContentView.
as Gene said, there should only be a single NavigationView at the root of you navigation stack. This means that the next time you need to navigate to a page in a different View, you will add a NavigationLink but not wrap it in a NavigationView. So in the code that you initially posted, you need to remove the NavigationView from your SecView View but still keep the NavigationLink. See the code below:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination:SecView()) {
Text("Primo")
}
}
}
}
struct SecView: View {
var body: some View {
NavigationLink(destination:TerView()) {
Text("Secondo")
}
}
}
struct TerView: View {
var body: some View {
Text("Hello World!")
}
}

Multiple NavigationLink in SwiftUI

Can you have multiple NavigationLinks in SwiftUI? The following only displays the first Link:
struct Test : View {
var body: some View {
NavigationView {
NavigationLink(destination: Text("First")) {
Text("Visible")
}
NavigationLink(destination: Text("Second")) {
Text("Invisible")
}
//EDIT: Also Invisible
Text("Not rendered")
}
}
}
EDIT: Turns out everything under the first NavigationLink is not displayed
Put your views inside a VStack:
struct Test : View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("First")) {
Text("Visible")
}
NavigationLink(destination: Text("Second")) {
Text("Invisible")
}
//EDIT: Also Invisible
Text("Not rendered")
}
}
}
}
Look you can definitely have multiple NavigationLinks but here you are doing one things wrong.
The body property returns a single View but here you are trying to return more than one views, which causes the error.
To Solve this issue we put them inside another View like a VStack or a HStack, like shown above in the answer given by kontiki.

How to perform animated transitions of a view inside a ScrollView?

I have a ScrollView, which contains an HStack, which contains a ForEach, which contains some custom view with an attached transition. It looks something like:
ScrollView {
HStack {
ForEach(self.objects) { object in
ObjectView(object)
.transition(.slide)
}
}
}
When I update the contents of self.objects (within a call to withAnimation), I want the old/new ObjectViews to animate in and out as specified by the transition.
Unfortunately, no animated transitions appear. If I remove the ScrollView (leaving the HStack to be the root view), the animated transitions work as expected.
Is there a way I can animate the transitions of views inside a ScrollView?
maybe you are interested...this works:
struct ObjectView : View {
var text: String
var body: some View {
Text(text)
}
}
struct ContentView: View {
#State var objects = ["a", "b", "c"]
var body: some View {
Group() {
Button("add") {
withAnimation() {
self.objects.append("f")
}
}
ScrollView {
HStack {
ForEach(objects, id: \.self) { object in
ObjectView(text: object)
.transition(.slide)
}
}.frame(width: 500)
}
}
}
}