I'm writing a fairly simple SwiftUI app about movies and I have this issue where the new .searchable modifier on NavigationView is always being shown, whereas it should be hidden, unless you pull down on the List.
It hides it correctly if I scroll a bit up, and if I scroll down it also hides it correctly, but other than that, it's always being shown. See gif for clarification. (basically it should behave the same as in Messages app)
https://imgur.com/R2rsqzh
My code for using the searchable is fairly simple:
var body: some View {
NavigationView {
List {
ForEach(/*** movie stuff ***/) { movie in
///// row here
}
}
.listStyle(GroupedListStyle())
.onAppear {
// load movies
}
.navigationTitle("Discover")
.searchable(text: $moviesRepository.searchText, placement: .toolbar, prompt: "Search...")
}
}
}
So, after adding a progress view above the list view, it suddenly started working the way I want it to. The code now is minimally changed and looks like this.
var body: some View {
NavigationView {
VStack {
if /** we're doing search, I'm checking search text **/ {
ProgressView()
.padding()
}
if !movies.isEmpty {
List {
ForEach(/** movies list **/) { movie in
// movie row here
}
}
.listStyle(GroupedListStyle())
.navigationTitle("Discover")
.searchable(text: $moviesRepository.searchText, placement: .toolbar,
prompt: Text("Search...")
.foregroundColor(.secondary)
)
} else {
ProgressView()
.navigationTitle("Discover")
}
}
}
.onAppear {
// load movies
}
}
And basically after adding the progress views, it started working the way I described it in my OP and the way it worked for ChrisR
Related
How can I prevent a view within a Sheet from inheriting components from its parents?
I believed a Sheet would create a whole new view hierarchy, but apparently it does not completely. As seen in the Gif below, the ListView inherits the Form when it is attached to the Button inside the Form. When placing it outside the form, the List behaves as expected. Is there a way to prevent this from happening? As you can see, the list with its sections becomes pretty useless when inheriting the Form. This almost seems like a bug to me.
Tested in an empty project using Xcode Version 12.5 (12E262) and iOS 14.5
import SwiftUI
struct ContentView: View {
#State var isSheetPresented: Bool = false
var body: some View {
NavigationView {
Form {
NavigationLink ("Link", destination: ListView())
Button("Sheet") {
isSheetPresented = true
}
.sheet(isPresented: $isSheetPresented, content: {
NavigationView {
ListView()
}
})
}
}
}
}
struct ListView: View {
var body: some View {
List {
Button("1") { }
Button("2") { }
Button("3") { }
Section(header: Text("one")) {
Button("4") { }
}
}
.navigationBarTitle("List")
}
}
Use DefaultListStyle()
ListView().listStyle(DefaultListStyle())
I have a View with a list with navigationLinks, and show a list of items to DetailView, and DetailView can go to another View and so on. Therefore it will have a full navigation stack in here.
However, in our app, we have two ways to show this view, first, a presenting View to show this view. Second, by other navigationLink under another NavigationView to push to this view. In the first case, it is fine. however in second case, it will show nested navigation bar, which I don't really like it.
Is there any possible way to show the following view without any nested navigationBar, in the pushing and presenting(UIKit wording) way
var body: some View {
NavigationView {
List(items) { item in
NavigationLink(destination: DetailView(item)) {
ItemRow(item)
}
}
.navigationBarTitle("Item list")
}
}
thanks a lot!
As Asperi says, you must separate the View into two Views. You have double NavigationBar because in the case where you already enter with the previous NavigationView at the top level of the View hierarchy, having another NavigatioView makes it double the NavigationBar.
You must do something like this in the View before this one:
var body: some View {
VSTack {
Text("Hello Word")
Button(action: {
if isShowWithPresent { // Some Bool
NavigationView {
SomeView()
}
} else {
AnotherView() // Some View without the NavigationView in the code
}
}) {
Text("Hit Me!")
}
}
}
I have a view which displays a list of posts. I have implemented infinite scrolling, and it is functioning properly. however, there is one small problem I am running into, and attempts to solve it have me going round in circles.
Main view
struct PostsHomeView: View {
#EnvironmentObject var viewModel: ViewModel
#State var dataInitiallyFetched = false
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 0) {
if self.viewModel.posts.count > 0 {
PostsListView(posts: self.viewModel.posts,
isLoading: self.viewModel.canFetchMorePosts,
onScrolledAtBottom: self.viewModel.fetchMorePosts
)
} else {
VStack {
Text("You have no posts!")
}
}
}
.onAppear() {
if !self.dataInitiallyFetched {
self.viewModel.fetchMostRecentPosts()
self.dataInitiallyFetched = true
}
}
.navigationBarTitle("Posts", displayMode: .inline)
}
}
}
List view
struct PostsListView: View {
#EnvironmentObject var viewModel: ViewModel
let posts: [Post]
let isLoading: Bool
let onScrolledAtBottom: () -> Void
var body: some View {
List {
postsList
if isLoading {
loadingIndicator
}
}
}
private var postsList: some View {
ForEach(posts, id: \.self) { post in
PostsCellView(post: post)
.onAppear {
if self.posts.last == post {
self.onScrolledAtBottom()
}
}
}
.id(UUID())
}
}
Problem
Upon tapping one of the posts in the list, I am taken to a detail view. When I tap the nav bar's back button in order go back to the posts list, the whole view is reloaded and my post fetch methods are fired again.
In order to stop the fetch method that fetches most recent posts from firing, I have added a flag that I set to true after the initial load. This stops the fetch method that grabs the initial set of posts from firing when I go back and forth between the details view and posts home screen.
I have tried various things to stop the fetchMorePosts function from firing, but I keep going in circles. I added a guard statement to the top of the fetchMorePosts function in my view model. It checks to see if string is equal to "homeview", if not, then the fetch is not done. I set this string to "detailview" whenever the detail view is visited, then I reset it back to "homeview" in the guard statement.
guard self.lastView == "homeview" else {
self.lastView = "homeview"
return
}
This works to an extent, but I keep finding scenarios where it doesn't work as expected. There must be a straight-forward way to tell SwiftUI not to reload a view. The problem is the method sits in the onAppear closure which is vital for the infinite scrolling to work. I'm not using iOS 14 yet, so I can't use #StateObject.
Is there a way to tell SwiftUI not to fire onAppear everytime I return from a detail view?
Thanks in advance
The culprit was .id(UUID()). I removed it from my list and everything worked again.
Thanks Asperi. Your help is much appreciated.
This question already has answers here:
Multiple sheet(isPresented:) doesn't work in SwiftUI
(21 answers)
Closed 2 years ago.
I am trying to attach multiple modal views to a NavigationView using .sheet presentation.
I tried chaining the .sheet together just to discover that only the last one can be triggered to display when bind variable is changed
Is there a way to do this for multiple modals?
var body: some View {
NavigationView {
ZStack(alignment: .leading) {
MainView()
}
// present profile page
.sheet(isPresented: self.$presentation.profile){
ProfilePage()
}
// present product page
.sheet(isPresented: self.$presentProduct) {
SingleProductView()
}
//present login
.sheet(isPresented: self.$showLogin) {
LoginView(showLogin:self.$showLogin)
}
//present cart
.sheet(isPresented: self.$showCart) {
CartView()
}
// set title
.navigationBarTitle("Title", displayMode: .inline)
// set items
.navigationBarItems(leading: (
NavigationBarLeadingItems()
),trailing: (
NavigationBarTrailingItems()
)
)
Calling the same method multiple times on the same Element in SwiftUI will always result in only the last one being applied.
Text("Some Nice Text")
.forgroundColor(.red)
.forgroundColor(.blue)
This will always result in the Text to be displayed in blue and not in red, even thou you set its color to red. Same gos for you .sheet call. The last .sheet call will kinda override its predecesors.
There are two possible solutions:
you place the .sheet call on different Views.
ViewOne().sheet(...) { ... }
ViewSecond().sheet(...) { ... }
you change the content of you .sheet call dynamically:
View()
.sheet(...) {
if someState {
return SheetViewOne()
else {
return SheetViewSecond()
}
}
}
After clicking the picker it navigates to the select view. The item list is rendered too far from the top, but snaps up after the animation is finished. Why is this happening?
Demo: https://gfycat.com/idioticdizzyazurevase
I already created a minimal example to rule out navigation bar titles and buttons, form sections and other details:
import SwiftUI
struct NewProjectView: View {
#State var name = ""
var body: some View {
NavigationView {
Form {
Picker("Client", selection: $name) {
Text("Client 1")
Text("Client 2")
}
}
}
}
}
struct NewProjectView_Previews: PreviewProvider {
static var previews: some View {
NewProjectView()
}
}
This happens in preview mode, simulator and on device (Xcode 11.2, iOS 13.2 in simulator, 13.3 beta 1 on device).
The obviously buggy behavior can be worked around when forcing the navigation view style to stacked:
NavigationView {
…
}.navigationViewStyle(StackNavigationViewStyle())
This is a solution to my problem, but I won‘t mark this as accepted answer (yet).
It seems to be a bug, even if it may be triggered by special circumstances.
My solution won‘t work if you need another navigation view style.
Additionally, it won‘t fix the horizontal repositioning mentioned by DogCoffee in the comments.
In my opinion, it has something to do with the navigation bar. In default (no mention of .navigationBarTitle extension), the navigation display mode is set to .automatic, this should be amend to .inline. I came across another post similar to this and use their solution to combine with yours, by using .navigationBarTitle("", displayMode: .inline) should help.
import SwiftUI
struct NewProjectView: View {
#State var name = ""
var body: some View {
NavigationView {
Form {
Picker("Client", selection: $name) {
Text("Client 1")
Text("Client 2")
}
}
.navigationBarTitle("", displayMode: .inline)
}
}
}
struct NewProjectView_Previews: PreviewProvider {
static var previews: some View {
NewProjectView()
}
}
Until this bug is resolved another way to work around this issue while retaining the DoubleColumnNavigationViewStyle for iPads would be to conditionally set that style:
let navView = NavigationView {
…
}
if UIDevice.current.userInterfaceIdiom == .pad {
return AnyView(navView.navigationViewStyle(DoubleColumnNavigationViewStyle()))
} else {
return AnyView(navView.navigationViewStyle(StackNavigationViewStyle()))
}
Thanks for this thread everyone! Really helped me understand things more and get a hold of one of my problems. To share with others, I was having this problem to but I was also having this problem when I set a section to appear in a if/else statement set on a section with a toggle. When the toggle was activated it would shift the section header horizontally a few pixels.
The following is how I fixed it
Section(header: Text("Subject Identified").listRowInsets(EdgeInsets()).padding(.leading)) {
Picker(selection: $subIndex, label: Text("Test")) {
ForEach(0 ..< subIdentified.count) {
Text(self.subIdentified[$0]).tag($0)
}
}
.labelsHidden()
.pickerStyle(SegmentedPickerStyle())
I'm still having horizontal shift on my picker selection view and not sure how to fix. I created another thread to received input. Thanks again! SwiftUI Shift Picker Text Horizontal