I have a basic list in my first View as Follows:
func buildList(sections: [Client]) -> some View {
let list = List {
ForEach(sections) { client in
Section(header: Text(client.name)) {
ForEach(client.projects) { project in
NavigationLink(destination: BuildsRouter.build(forProject: project)) {
HStack {
Text("Test \(project.id)").fontWeight(.ultraLight)
}
}
}
}
}
}
return list
}
I'm using NavigationLink to provide the details view for my Project object.
Thing is, when I make a Memory analysis graph I can see that BuildsView ( created from BuildsRouter.build(forProject: project) are created before I actually tap the navigation Link.
Question:
Is there any way to create the details View once the link is tapped?
True, I wrote a blog post on this. You can wrap the destination views in a Lazy Container as a workaround. Update: From Xcode 11.4.1 NavigationLinks are lazy by default.
Related
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
This tutorial uses a NavigationView to display a List of elements which can be clicked, leading to a detailed view, LandmarkDetail. On an iPhone, the UI uses the StackNavigationViewStyle() which looks and works fine, but on an iPad the NavigationView is displayed on the side. I want to be able to fill up the remaining space with a detailed view (see below).
I have tried to display the detailed view, LandmarkDetail, beside the NavigationView like so:
var body: some View {
NavigationView {
List {
Toggle(isOn: $showFavoritesOnly) {
Text("Favorites only")
}
ForEach(filteredLandmarks) { landmark in
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
.navigationTitle("Landmarks")
LandmarkDetail(landmark: ModelData().landmarks[0])
}
}
This approach works fine for the iPad, but produces unexpected results (overlapping tiles, etc.) on the iPhone (see below). Is there a better way for achieving my desired results?
Thanks for any help and I apologise about potential obvious mistakes, etc. This is my first time using SwiftUI and there are noticeably less resources available than for Android development.
Creating a modifier which programmatically assigned the NavigationView style depending on the target device fixed this issue.
extension View
{
func navigationStyleModification() -> some View
{
if UIDevice.current.userInterfaceIdiom == .phone
{
return AnyView(self.navigationViewStyle(StackNavigationViewStyle()))
} else
{
return AnyView(self.navigationViewStyle(DefaultNavigationViewStyle()))
}
}
}
The modifier can be called via:
NavigationView
{
...
}.navigationStyleModification()
I hope this helps someone who has a similar issue.
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()
}
}
}
I have a List of contacts, and on clicking I want to navigate to the detail view. Here is my ListingView code:
var body: some View {
NavigationView {
List(viewModel.tableViewItems) { contact in
NavigationLink(destination: ContactDetailView(viewModel: ContactDetailViewModel(contact: contact))) {
Text(contact.fullName)
}
}
}
}
This is what I found online and it works. But I found out that the view and the viewModel of the destination were getting initialised just after the List is populated, without even selecting. Is this correct or am I not understanding something properly?
I'm using a NavigationLink inside of a ForEach in a List to build a basic list of buttons each leading to a separate detail screen.
When I tap on any of the list cells, it transitions to the detail view of that cell but then immediately pops back to the main menu screen.
Not using the ForEach helps to avoid this behavior, but not desired.
Here is the relevant code:
struct MainMenuView: View {
...
private let menuItems: [MainMenuItem] = [
MainMenuItem(type: .type1),
MainMenuItem(type: .type2),
MainMenuItem(type: .typeN),
]
var body: some View {
List {
ForEach(menuItems) { item in
NavigationLink(destination: self.destination(item.destination)) {
MainMenuCell(menuItem: item)
}
}
}
}
// Constructs destination views for the navigation link
private func destination(_ destination: ScreenDestination) -> AnyView {
switch destination {
case .type1:
return factory.makeType1Screen()
case .type2:
return factory.makeType2Screen()
case .typeN:
return factory.makeTypeNScreen()
}
}
If you have a #State, #Binding or #ObservedObject in MainMenuView, the body itself is regenerated (menuItems get computed again) which causes the NavigationLink to invalidate (actually the id change does that). So you must not modify the menuItems arrays id-s from the detail view.
If they are generated every time consider setting a constant id or store in a non modifying part, like in a viewmodel.
Maybe I found the reason of this bug...
if you use iOS 15 (not found iOS 14),
and you write the code NavigationLink to go to same View in different locations in your projects, then this bug appear.
So I simply made another View that has different destination View name but the same contents... then it works..
you can try....
sorry for my poor English...