what I am trying to achieve is creating a hierarchical view. I understand that iOS simply doesn't like to use breadcrumbs but I need to navigate from a main view in to deeper subviews. they need to be nested and infinite.
you can see what I've done so far in the code and gif below. As I'm a beginner developer I'm not sure if this is the right way to achieve this kind of structure (infinite sub-views nested inside sub-views). Also when I navigate back in views, added buttons(struct A) disappears. What seems to be the problem?
Thanks in advance!
code in action gif
import SwiftUI
struct A: View, Identifiable {
#EnvironmentObject var documentB: classB
var id: Int
var text: String
var destinationLink: B?
var body: some View {
NavigationLink(destination: self.destinationLink) {
VStack{
Rectangle()
.frame(width: 35, height:25)
.background(Color.red)
Text("\(text)")
}
}
}
}
struct B: View, Identifiable {
#EnvironmentObject var documentB: classB
#State var arrayA: [A] = []
var id: Int
var text: String
var mainText: String = "Placeholder"
var body: some View {
NavigationView {
VStack {
Spacer()
ForEach(arrayA){ item in
item
}
Spacer()
Button(action: {
let newB = B(id:self.documentB.arrayB.count+1, text:"B \(self.documentB.arrayB.count+1)")
self.documentB.arrayB.append(newB)
self.arrayA.append(A(id:self.arrayA.count+1, text:"AA \(self.arrayA.count+1)", destinationLink: newB))
}) {
Text("Add A \(self.arrayA.count), B Count: \(self.documentB.arrayB.count)")
}
}
.navigationBarTitle(text)
}
}
}
class classB: ObservableObject {
#Published var arrayB: [B] = [B(id:1, text:"MainView")]
}
struct ContentView: View {
#ObservedObject var documentB = classB()
var body: some View {
VStack {
documentB.arrayB[0]
}
.environmentObject(documentB)
}
}
You just need to move NavigationView into ContentView, because the only one is needed on one view hierarchy, so
struct ContentView: View {
#ObservedObject var documentB = classB()
var body: some View {
NavigationView { // << move it here from B
VStack {
documentB.arrayB[0]
}
}
.environmentObject(documentB)
}
}
Related
I'm new to swiftUI here and I want to try out to pass data between two views. But it doesn't seem to work.
I'm using Xcode 13.2 & iOS 15 for the simulator.
This is my code for the first view:
struct ContentView: View {
#State var myName: String = ""
var body: some View {
NavigationView {
VStack {
TextField("Enter your name", text: $myName)
Text(self.myName)
NavigationLink(destination: BView(myName: self.$myName), label: {
Image(systemName: "arrowshape.turn.up.left")
})
}//: VSTACK
.padding(.horizontal, 20)
}//:NAVIGATION VIEW
}
}
This is code for the second view:
struct BView: View {
#Binding var myName: String
var body: some View {
NavigationView {
Text("BView")
Text(self.myName)
}//:NAVIGATION VIEW
}
}
I want myName to be input in the first page which is ContentView() and then pass down the input data to BView().
Unfortunately, once I run it on the simulator, the input data doesn't;t show up.
Your code is fine just add VStack in BView.
struct BView: View {
#Binding var myName: String
var body: some View {
NavigationView {
VStack { // HERE
Text("BView")
Text(self.myName)
}
}//:NAVIGATION VIEW
}
}
Please use #EnvironmentObject to pass the data to view.
https://developer.apple.com/documentation/swiftui/environmentobject
I'm new to SwiftUI and struggling to move MainView to LoginView by clicking Text() in ChildView.
I tried make NavigationView and NavigationLink like this code but it's not working. It's much more complex in real code but I show only simple structure of my code to explain.
struct ContentView: View {
#State private var currViewIdx: Int = 0
var body: some View {
NavigationView {
CurrentView(currViewIdx: $currViewIdx)
}
}
}
struct CurrentView: View {
#Binding var currViewIdx: Int
var body: some View {
if self.currViewIdx == 0 {
HomeView()
.onTapGesture {
self.currViewIdx = 0
}
} else {
//SomeView.onTapGesture(self.currViewIdx = 1)
}
}
}
struct HomeView: View {
var body: some View {
NavigationLink(destination: TestView()) {
Text("Go TestView")
.contentShape(Rectangle())
}
}
}
struct TestView: View {
var body: some View {
Text("Here is Test View")
}
}
I think, the top hierarchy view, ContentView, has NavigationView and it should be changed when I click Text("Go TestView") in HomeView because it's in NavagationLink.
But the ContentView is not changed to TestView although I touch Text("Go TestView"). How to solve this problem? I considered way to add one more State value to change top level view, but it seems not good if I'll make lots of more values
Thank you
I'm working with SwiftUI and I have a starting page. When a user presses a button on this page, a modal sheet pops up.
In side the modal sheet, I have some code like this:
NavigationLink(destination: NextView(), tag: 2, selection: $tag) {
EmptyView()
}
and my modal sheet view is wrapped inside of a Navigation View.
When the value of tag becomes 2, the view does indeed go to NextView(), but it's also presented as a modal sheet that the user can swipe down from, and I don't want this.
I'd like to transition from a modal sheet to a regular view.
Is this possible? I've tried hiding the navigation bar, etc. but it doesn't seem to make a difference.
Any help with this matter would be appreciated.
You can do this by creating an environmentObject and bind the navigationLink destination value to the environmentObject's value then change the value of the environmentObject in the modal view.
Here is a code explaining what I mean
import SwiftUI
class NavigationManager: ObservableObject{
#Published private(set) var dest: AnyView? = nil
#Published var isActive: Bool = false
func move(to: AnyView) {
self.dest = to
self.isActive = true
}
}
struct StackOverflow6: View {
#State var showModal: Bool = false
#EnvironmentObject var navigationManager: NavigationManager
var body: some View {
NavigationView {
ZStack {
NavigationLink(destination: self.navigationManager.dest, isActive: self.$navigationManager.isActive) {
EmptyView()
}
Button(action: {
self.showModal.toggle()
}) {
Text("Show Modal")
}
}
}
.sheet(isPresented: self.$showModal) {
secondView(isPresented: self.$showModal).environmentObject(self.navigationManager)
}
}
}
struct StackOverflow6_Previews: PreviewProvider {
static var previews: some View {
StackOverflow6().environmentObject(NavigationManager())
}
}
struct secondView: View {
#EnvironmentObject var navigationManager: NavigationManager
#Binding var isPresented: Bool
#State var dest: AnyView? = nil
var body: some View {
VStack {
Text("Modal view")
Button(action: {
self.isPresented = false
self.dest = AnyView(thirdView())
}) {
Text("Press me to navigate")
}
}
.onDisappear {
// This code can run any where but I placed it in `.onDisappear` so you can see the animation
if let dest = self.dest {
self.navigationManager.move(to: dest)
}
}
}
}
struct thirdView: View {
var body: some View {
Text("3rd")
.navigationBarTitle(Text("3rd View"))
}
}
Hope this helps, if you have any questions regarding this code, please let me know.
I have a navigation requirement that looks something like this:
Each detail screen can navigation to the next and previous detail screen. At the same time, the "back" button should always go back to the main list (not the previous detail screen).
I'm struggling with how to accomplish this in SwiftUI?
Here is what I have so far:
struct ListView: View {
#State private var currentDetailShown: Int?
#State private var listItems: [Int] = Array(repeating: 0, count: 10)
func goToNext() {
if let idx = self.currentDetailShown {
self.currentDetailShown = min(self.listItems.count - 1, idx + 1)
}
}
func goToPrev() {
if let idx = self.currentDetailShown {
self.currentDetailShown = max(0, idx - 1)
}
}
var body: some View {
List {
ForEach(0..<listItems.count) { index in
NavigationLink(destination: DetailView(goToNext: self.goToNext, goToPrev: self.goToPrev),
tag: index,
selection: self.$currentDetailShown) {
ListItem(score: listItems[index])
}
.isDetailLink(false)
.onTapGesture {
self.currentDetailShown = index
}
}
}
}
}
What happens with this code is that from the first detail view, it'll move to the to the next detail view and then immediately jump back to the list view.
I feel like I'm overthinking this or missing something obvious...
Instead of navigating to each detail from your list, you can navigate to a detailView that can show each detail individually by using a published variable in an observable object. Here is an example
struct MainView: View{
#EnvironmentObject var viewModel: ViewModel
var body: some View{
NavigationView{
VStack{
ForEach(self.viewModel.details, id:\.self){ detail in
NavigationLink(destination: DetailView(detail: self.viewModel.details.firstIndex(of: detail)!).environmentObject(ViewModel())){
Text(detail)
}
}
}
}
}
}
class ViewModel: ObservableObject{
#Published var showingView = 0
#Published var details = ["detail1", "detail2", "detail3", "detail4", "detail5", "detail6"]
}
struct DetailView: View{
#EnvironmentObject var viewModel: ViewModel
#State var detail: Int
var body: some View{
VStack{
IndivisualDetailView(title: viewModel.details[detail])
Button(action: {
self.viewModel.showingView -= 1
}, label: {
Image(systemName: "chevron.left")
})
Button(action: {
self.viewModel.showingView += 1
print(self.viewModel.showingView)
}, label: {
Image(systemName: "chevron.right")
})
}
}
}
struct IndivisualDetailView: View{
#State var title: String
var body: some View{
Text(title)
}
}
I'm trying to write an app using SwiftUI and the MVVM architecture. I understand how a view is automatically updated when its view model changes, but I don't get how I can access that view model in a parent view. For example, I have this view which contains a text field with a red background:
class CustomTextFieldViewModel: ObservableObject {
#Published var text: String = "abc"
}
struct CustomTextField: View {
#ObservedObject var viewModel: CustomTextFieldViewModel
var body: some View {
ZStack {
Color.red
.frame(height: 50)
TextField("Enter text...", text: $viewModel.text)
}
}
}
Then I have my main content view with an instance of this CustomTextField as well as a Text that's supposed to reference the text field's text:
class ContentViewModel: ObservableObject {
#Published var textFieldModel = CustomTextFieldViewModel()
}
struct ContentView: View {
#ObservedObject var viewModel = ContentViewModel()
var body: some View {
VStack {
CustomTextField(viewModel: $viewModel.textFieldModel)
TextField("type", text: $viewModel.textFieldModel.text)
}
}
}
When I first launch the app, the Text label does show the initial value of "abc", but when I type in the text field this doesn't update.
How can I "synchronise" these both?
Here is fix
struct ContentView: View {
#ObservedObject var viewModel = ContentViewModel()
var body: some View {
VStack {
CustomTextField(viewModel: viewModel.textFieldModel) // << fix !!
Text("\(viewModel.textFieldModel.text)")
}
}
}
I ended up fixing it by doing this:
class ContentViewModel: ObservableObject {
#Published var textFieldModel = CustomTextFieldViewModel()
}
struct ContentView: View {
#ObservedObject var viewModel = ContentViewModel()
var body: some View {
VStack {
CustomTextField(viewModel: $viewModel.textFieldModel)
Text("\($viewModel.textFieldModel.text.wrappedValue)") //<< wrappedValue
}
}
}
and
class CustomTextFieldViewModel: ObservableObject {
#Published var text: String = "abc"
}
struct CustomTextField: View {
#Binding var viewModel: CustomTextFieldViewModel //<< #Binding
var body: some View {
ZStack {
Color.red
.frame(height: 50)
TextField("Enter text...", text: $viewModel.text)
}
}
}