new navigation stack swiftui 4 - swiftui

Hello I'm using the new navigation stack and I don't really understand how it works in a situation like mine. I have the first view (DashboardView) from there I have two navigationdestination(isPresented) and from the second one I have to go a view (AllVocabulariesView) with a list and after every row has to go to another view (VocabularyView) that is the save that I can reach from the DashboardView with the first navigationdestination(isPresented).
here below the DashboardView (I remove unnecessary code)
NavigationStack {
VStack {
LazyVGrid(columns: columns, alignment: .center, spacing: spacing, pinnedViews: [])
{
ForEach(vmHome.vocabularies.prefix(5)) { vocabulary in
VocabularyBannerView(name: vocabulary.name ?? "", language: vmHome.getLanguageName(code: vocabulary.language ?? ""), isFavourite: vocabulary.isFavourite)
.onTapGesture {
selectedVocabulary = vocabulary
showVocabularyView.toggle()
}
.contextMenu {
Button {
vmHome.setVocabularyAsFavourite(entity: vocabulary)
} label: {
Label("Favourite", systemImage: "star")
}
Button(role: .destructive) {
vmHome.deleteVocabulary(vocabulary: vocabulary)
} label: {
Label("Delete", systemImage: "trash")
}
} preview: {
VocabularyBannerView(name: vocabulary.name ?? "", language: vmHome.getLanguageName(code: vocabulary.language ?? ""), isFavourite: vocabulary.isFavourite)
}
}
}
Button {
showAllVocabularyView.toggle()
} label: {
Text("Show all")
.font(.caption)
}
}
.navigationTitle("Dashboard")
.toolbar(.hidden, for: .navigationBar)
.navigationDestination(isPresented: $showVocabularyView) {
if selectedVocabulary != nil {
VocabularyView(vmHome: vmHome, vocabulary: selectedVocabulary!)
}
}
.navigationDestination(isPresented: $showAllVocabularyView) {
AllVocabulariesView(vmHome: vmHome)
}
}
so the first one goes to the VocabularyView and it's fine also the second goest to AllVocabulariesView but now the problem is inside the AllVocabulariesView that I have structured like this
NavigationStack{
List {
ForEach(vmHome.vocabularies) { vocabulary in
NavigationLink(value: vocabulary) {
Text(vocabulary.name ?? "")
}
}
}
.navigationDestination(for: VocabularyEntity.self, destination: { vocabulary in
VocabularyView(vmHome: vmHome, vocabulary: vocabulary)
})
.navigationTitle("Your vocabularies")
.navigationBarTitleDisplayMode(.inline)
}
two thinks if I remove the navigationstack the row has being highlighted but nothing happen and with navigationstack it goest to the view but without animation and the back button goest back to DashboardView instead of AllVocabulariesView

After testing in several applications, I have come to the conclusion that NavigationStack should not be nested.
In fact, you can use the .navigationDestination at the top level and it will keep working for all nested NavigationLink.
Although I cannot test without the full code, I suggest you do the following modifications:
Remove the NavigationStack from your AllVocabulariesView
Group your .navigationDestination at the top level where you have put the other ones
DashboardView:
NavigationStack {
VStack {
LazyVGrid(columns: columns, alignment: .center, spacing: spacing, pinnedViews: [])
{
ForEach(vmHome.vocabularies.prefix(5)) { vocabulary in
VocabularyBannerView(name: vocabulary.name ?? "", language: vmHome.getLanguageName(code: vocabulary.language ?? ""), isFavourite: vocabulary.isFavourite)
.onTapGesture {
selectedVocabulary = vocabulary
showVocabularyView.toggle()
}
.contextMenu {
Button {
vmHome.setVocabularyAsFavourite(entity: vocabulary)
} label: {
Label("Favourite", systemImage: "star")
}
Button(role: .destructive) {
vmHome.deleteVocabulary(vocabulary: vocabulary)
} label: {
Label("Delete", systemImage: "trash")
}
} preview: {
VocabularyBannerView(name: vocabulary.name ?? "", language: vmHome.getLanguageName(code: vocabulary.language ?? ""), isFavourite: vocabulary.isFavourite)
}
}
}
Button {
showAllVocabularyView.toggle()
} label: {
Text("Show all")
.font(.caption)
}
}
.navigationTitle("Dashboard")
.toolbar(.hidden, for: .navigationBar)
.navigationDestination(isPresented: $showVocabularyView) {
if selectedVocabulary != nil {
VocabularyView(vmHome: vmHome, vocabulary: selectedVocabulary!)
}
}
.navigationDestination(isPresented: $showAllVocabularyView) {
AllVocabulariesView(vmHome: vmHome)
}
.navigationDestination(for: VocabularyEntity.self, destination: { vocabulary in
VocabularyView(vmHome: vmHome, vocabulary: vocabulary)
})
}
AllVocabulariesView:
List {
ForEach(vmHome.vocabularies) { vocabulary in
NavigationLink(value: vocabulary) {
Text(vocabulary.name ?? "")
}
}
}
.navigationTitle("Your vocabularies")
.navigationBarTitleDisplayMode(.inline)

Related

Animate text position simultaneously with text changing in SwiftUI

I want to animate the position of Expand button, but instead it appears right at the final position. There's a strange hack .transition(.scale) that fixes my problem, but I hope to see a better not hacky solution.
struct TextView: View {
#State var isExpanded = false
var body: some View {
VStack(spacing: 10) {
Text("This is the most recent test comment with a picture. Lörem ipsum berölogi kjolprotest. Trist gigade. Sms-livräddare grönt elcertifikat.")
.lineLimit(isExpanded ? nil : 2)
HStack {
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Spacer()
Text(isExpanded ? "Less" : "Expand")
}
// .transition(.scale)
}
}
.padding(.all)
.background(Color.yellow)
.cornerRadius(13)
.padding(.horizontal)
}
}
One option is to use a ZStack containing Buttons in both states, and use the .opacity modifier to hide or show them…
struct ContentView: View {
#State var isExpanded = false
var body: some View {
VStack(spacing: 10) {
Text("This is the most recent test comment with a picture. Lörem ipsum berölogi kjolprotest. Trist gigade. Sms-livräddare grönt elcertifikat.")
.lineLimit(isExpanded ? nil : 2)
HStack {
ZStack {
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Spacer()
Text("Less")
}
.opacity(isExpanded ? 1 : 0)
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Spacer()
Text("Expand")
}
.opacity(isExpanded ? 0 : 1)
}
}
}
.padding(.all)
.background(Color.yellow)
.cornerRadius(13)
.padding(.horizontal)
}
}
Or alternatively, use .matchedGeometryEffect
if isExpanded {
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Spacer()
Text("Less")
}
.matchedGeometryEffect(id: "button", in: namespace)
} else {
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Spacer()
Text("Expand")
}
.matchedGeometryEffect(id: "button", in: namespace)
}
Obviously there's some duplication so you would pull the Button out into a func or its own View struct.

How to add horizontal carousel above List in SwiftUI

I'm trying to recreate the layout of the native Watch app which has a carousel with a List underneath it. How can get the carousel in the first section to scroll off the edges of the screen instead of cut off?
Here's the code I'm trying:
let data: [String] = (0..<25).map { "Item \($0)" }
let data2: [String] = (0..<3).map { "Item \($0)" }
NavigationView {
List {
Section {
ScrollView(.horizontal) {
HStack {
ForEach(data) { element in
VStack {
Text(element)
}
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(10)
}
}
}
}
.listRowBackground(Color.clear)
.listRowInsets(.init())
Section("Section 1") {
ForEach(data2) { element in
NavigationLink {
Text("Destination")
} label: {
Label(element, systemImage: "calendar.circle.fill")
}
}
}
}
.searchable(text: .constant(""), placement: .navigationBarDrawer(displayMode: .always))
.navigationTitle("Watch")
}
This is the native watch app I'm trying to replicate:

Restructure NavigationLink to Button for LongPressGesture

I am trying to put an option for .onlongpressgesture for this list of groups. However, it doesn't seem to work and I figure, it will work on Button.
I have tried to apply ".onTapGesture" and ".onLongPressGesture", but there is no effect.
Is there a way I can transform the Code below from a NavigationLink to a Button with the same destination when tapped and an additional Menu (called "OptionsmenuView") when long pressed?
The NavigationLink:
VStack (spacing: 20){
ForEach(groups, id:\.self) { Group in
NavigationLink(destination: GroupView()) {
ZStack (alignment: .bottomLeading) {
Image(uiImage: (UIImage(data: Group.groupThumbnail ?? self.image) ?? UIImage(named: "defaultGroupThumbnail"))!)
.resizable(capInsets: EdgeInsets())
.aspectRatio(contentMode: .fill)
.frame(height: 200.0, alignment: .center)
.cornerRadius(22)
VStack (alignment: .leading) {
Text("\(Group.groupTitle ?? "Untitled")")
.font(.title)
.fontWeight(.heavy)
.multilineTextAlignment(.leading)
Text("Consists of 5 Flowers")
}
.padding([.leading, .bottom], 18.0)
.foregroundColor(.primary)
}
.listRowBackground(Color.black)
}
}
}
The OptionMenuView:
struct OptionsMenuView: View {
var body: some View {
Menu {
Button("Cancel", role: .destructive) {
// Do something
}
Button {
// Do something
} label: {
Label("Edit", systemImage: "square.and.pencil")
}
Button {
// Do something
} label: {
Label("Delete", systemImage: "trash")
}
} label: {
Label("Settings", systemImage: "gearshape.fill")
}
}
}
I appreciate any form of advice. Thanks in advance.
Couldn't you just use .contextMenu ?
NavigationView {
List (0..<10) { i in
NavigationLink {
Text("DestionationView")
} label: {
Text("Item \(i)")
}
.contextMenu { // << here
Button("Cancel", role: .destructive) {
// Do something
}
Button {
// Do something
} label: {
Label("Edit", systemImage: "square.and.pencil")
}
Button {
// Do something
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}

How to adjust SwiftUI List row separator inset with a Label like Settings app?

In SwiftUI, I haven't found an elegant method with SwiftUI List api to let List row separator be like settings app style, while the row including a Image and a Text (aka Label).
What I want is:
What I usually do:
```swift
// here is my common code
NavigationView {
List {
NavigationLink {
Text("Detail 1")
} label: {
Label {
Text("Row Title 1")
} icon: {
Image(systemName: "gear")
}
}
NavigationLink {
Text("Detail 3")
} label: {
Label {
Text("Row Title 3")
} icon: {
Image(systemName: "wifi")
}
}
NavigationLink {
Text("Detail 2")
} label: {
Label {
Text("Row Title 2")
} icon: {
Image(systemName: "person")
}
}
}
.listStyle(.insetGrouped)
.navigationTitle("Demo")
}
```

Buttons inside ToolbarItem cannot dismiss sheet

In Xcode 12 Beta 6, dismissing a sheet doesn't work inside a button's action inside a ToolbarItem.
My sheet view looks like:
NavigationView {
Form {
Section {
TextField("Name", text: $name)
}
}
.navigationTitle("New Thing")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(action: {
self.presentation.wrappedValue.dismiss()
}, label: {
Text("Cancel")
})
}
ToolbarItem(placement: .confirmationAction) {
Button(action: {
do {
// some saving logic
try managedObjectContext.save()
self.presentation.wrappedValue.dismiss()
} catch {
print("didn't save due to \(error.localizedDescription)")
}
}, label: {
Text("Save")
})
}
}
}
EDIT: here's how I constructed the sheet
var body: some View {
List {
ForEach(results) { result in
HStack {
NavigationLink(destination: SingleResultView(result: result)) {
SingleResultRowView(result: result)
}
}
}
.onDelete(perform: deleteResult)
}
.navigationTitle("All Results")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: {
self.isNewResultSheetPresented.toggle()
}, label: {
Image(systemName: "plus.circle.fill")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
.sheet(isPresented: $isNewResultSheetPresented) {
NewResultView()
// ^ this contains the code above
.environment(\.managedObjectContext, self.managedObjectContext)
}
}
}
}
When the sheet is first presented, immediately a console log appears:
2020-09-13 20:52:02.333679-0700 MyApp[2710:89263]
[Presentation] Attempt to present <_TtGC7SwiftUI22SheetHostingControllerVS_7AnyView_: 0x1027b7890> on
<_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x10270d620>
(from <_TtGC7SwiftUIP10$194f39bd428DestinationHostingControllerVS_7AnyView_: 0x103605930>)
which is already presenting <_TtGC7SwiftUI22SheetHostingControllerVS_7AnyView_: 0x103606d60>.
I can dismiss the sheet only by swiping down.
For reference, I went back to an older commit where I used NavigationBarItems and it worked perfectly. But from what I understand, this is a situation where I'm supposed to be using ToolbarItem.
Does anybody know why the good old self.presentation.wrappedValue.dismiss() doesn't work here or why is the sheet being presented twice?
Move sheet out of toolbar, as
var body: some View {
List {
ForEach(results) { result in
HStack {
NavigationLink(destination: SingleResultView(result: result)) {
SingleResultRowView(result: result)
}
}
}
.onDelete(perform: deleteResult)
}
.navigationTitle("All Results")
.sheet(isPresented: $isNewResultSheetPresented) { // << here !!
NewResultView()
// ^ this contains the code above
.environment(\.managedObjectContext, self.managedObjectContext)
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: {
self.isNewResultSheetPresented.toggle()
}, label: {
Image(systemName: "plus.circle.fill")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
}
}
}