How to pass a string to a child view? - swift

I want to pass the text in the textBox to the child view and create a scrollable Button there. As for the output status, we hope that 'a ~ c' are arranged vertically and that each is a button.
struct ContentView: View {
var textBox = ["a","b","c"]
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: false) {
ForEach(0..<textBox.count) { number in
ScrollText(text: self.textBox[number].lowercased())
}
}
}
}
}
struct ScrollText: View {
#Binding var text: String
#State private var flag: Bool = false
var body: some View {
Button(action: {
self.flag.toggle()
}) {
Text(text)
}
}
}

I'm not totally clear what the problem is, or what you want, but I solved some compiler errors in your code, and it's showing three buttons as expected:
struct ContentView : View {
var textBox = ["a","b","c"]
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: false){
ForEach(textBox, id: \.self) { letter in
ScrollText(text: letter)
}
}
}
}
}
struct ScrollText: View {
var text: String
#State private var flag: Bool = false
var body: some View {
Button(action: {
self.flag.toggle()
}, label: {
Text(text)
})
}
}
Your question was how to pass a string, so you don't need #Binding for that. Just pass a string :)

If you're going to keep ScrollText untouched the here is possible modifications in ContentView which uses it
struct ContentView: View {
#State private var textBox = ["a","b","c"] // < make State, so modifiable
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: false) {
ForEach(0..<textBox.count) { number in
ScrollText(text: self.$textBox[number]) // < pass Binding as intended
}
}
}
}
}

Related

Presenting Lists item in detail sheet swiftUI

I have a viewModel that with an item and a child view, I also present a sheet from a View and pass a selected item to that view. This in turn makes the selected item = item in the child view. The problem now is I have to dismiss the sheet and select a desired item be the value changes in the child view. This is a weird behaviour any help would be appreciated
My view Model
class ItemViewModel: ObservableObject {
#Injected(\.itemLocalRepository) var itemLocalRepository: ItemLocalRepository
#Published var items: [Item] { willSet { objectWillChange.send() } }
init(shoppingList: ShoppingList) {
self.shoppingList = shoppingList.item
// Printing shoppingList prints default value before changing to desired on on second selection
}
}
// Main View
struct FrequentView: View {
#State var selectedShoppingList: ShoppingList = ShoppingList.single
#State private var presentCreateSheet: Bool = false
var itemsList = [...]
var body: some View {
NavigationView {
ZStack {
ScrollView(.vertical, showsIndicators: false, content: {
LazyVStack(alignment: .leading, spacing: 15, pinnedViews: /*#START_MENU_TOKEN#*/[]/*#END_MENU_TOKEN#*/, content: {
ForEach(itemsList, id: \.id) { shoppingList in
Button {
self.selectedShoppingList = shoppingList
self.presentCreateSheet = true
} label: {
HomeRowView(shoppingList: shoppingList)
}
Divider()
.padding(.top, 0)
}
})
})
}
.sheet(isPresented: $presentCreateSheet, onDismiss: {
Task.init {
await viewModel.getList()
}
self.presentCreateSheet = false
}, content: {
ItemView(viewModel: ItemViewModel(shoppingList: selectedShoppingList))
})
}
}
}

How to update an element of an array in an Observable Object

Sorry if my question is silly, I am a beginner to programming. I have a Navigation Link to a detail view from a List produced from my view model's array. In the detail view, I want to be able to mutate one of the tapped-on element's properties, but I can't seem to figure out how to do this. I don't think I explained that very well, so here is the code.
// model
struct Activity: Identifiable {
var id = UUID()
var name: String
var completeDescription: String
var completions: Int = 0
}
// view model
class ActivityViewModel: ObservableObject {
#Published var activities: [Activity] = []
}
// view
struct ActivityView: View {
#StateObject var viewModel = ActivityViewModel()
#State private var showingAddEditActivityView = false
var body: some View {
NavigationView {
VStack {
List {
ForEach(viewModel.activities, id: \.id) {
activity in
NavigationLink(destination: ActivityDetailView(activity: activity, viewModel: self.viewModel)) {
HStack {
VStack {
Text(activity.name)
Text(activity.miniDescription)
}
Text("\(activity.completions)")
}
}
}
}
}
.navigationBarItems(trailing: Button("Add new"){
self.showingAddEditActivityView.toggle()
})
.navigationTitle(Text("Activity List"))
}
.sheet(isPresented: $showingAddEditActivityView) {
AddEditActivityView(copyViewModel: self.viewModel)
}
}
}
// detail view
struct ActivityDetailView: View {
#State var activity: Activity
#ObservedObject var viewModel: ActivityViewModel
var body: some View {
VStack {
Text("Number of times completed: \(activity.completions)")
Button("Increment completion count"){
activity.completions += 1
updateCompletionCount()
}
Text("\(activity.completeDescription)")
}
}
func updateCompletionCount() {
var tempActivity = viewModel.activities.first{ activity in activity.id == self.activity.id
}!
tempActivity.completions += 1
}
}
// Add new activity view (doesn't have anything to do with question)
struct AddEditActivityView: View {
#ObservedObject var copyViewModel : ActivityViewModel
#State private var activityName: String = ""
#State private var description: String = ""
var body: some View {
VStack {
TextField("Enter an activity", text: $activityName)
TextField("Enter an activity description", text: $description)
Button("Save"){
// I want this to be outside of my view
saveActivity()
}
}
}
func saveActivity() {
copyViewModel.activities.append(Activity(name: self.activityName, completeDescription: self.description))
print(copyViewModel.activities)
}
}
In the detail view, I am trying to update the completion count of that specific activity, and have it update my view model. The method I tried above probably doesn't make sense and obviously doesn't work. I've just left it to show what I tried.
Thanks for any assistance or insight.
The problem is here:
struct ActivityDetailView: View {
#State var activity: Activity
...
This needs to be a #Binding in order for changes to be reflected back in the parent view. There's also no need to pass in the entire viewModel in - once you have the #Binding, you can get rid of it.
// detail view
struct ActivityDetailView: View {
#Binding var activity: Activity /// here!
var body: some View {
VStack {
Text("Number of times completed: \(activity.completions)")
Button("Increment completion count"){
activity.completions += 1
}
Text("\(activity.completeDescription)")
}
}
}
But how do you get the Binding? If you're using iOS 15, you can directly loop over $viewModel.activities:
/// here!
ForEach($viewModel.activities, id: \.id) { $activity in
NavigationLink(destination: ActivityDetailView(activity: $activity)) {
HStack {
VStack {
Text(activity.name)
Text(activity.miniDescription)
}
Text("\(activity.completions)")
}
}
}
And for iOS 14 or below, you'll need to loop over indices instead. But it works.
/// from https://stackoverflow.com/a/66944424/14351818
ForEach(Array(zip(viewModel.activities.indices, viewModel.activities)), id: \.1.id) { (index, activity) in
NavigationLink(destination: ActivityDetailView(activity: $viewModel.activities[index])) {
HStack {
VStack {
Text(activity.name)
Text(activity.miniDescription)
}
Text("\(activity.completions)")
}
}
}
You are changing and increment the value of tempActivity so it will not affect the main array or data source.
You can add one update function inside the view model and call from view.
The view model is responsible for this updation.
class ActivityViewModel: ObservableObject {
#Published var activities: [Activity] = []
func updateCompletionCount(for id: UUID) {
if let index = activities.firstIndex(where: {$0.id == id}) {
self.activities[index].completions += 1
}
}
}
struct ActivityDetailView: View {
var activity: Activity
var viewModel: ActivityViewModel
var body: some View {
VStack {
Text("Number of times completed: \(activity.completions)")
Button("Increment completion count"){
updateCompletionCount()
}
Text("\(activity.completeDescription)")
}
}
func updateCompletionCount() {
self.viewModel.updateCompletionCount(for: activity.id)
}
}
Not needed #State or #ObservedObject for details view if don't have further action.

NavigationLink in List using isActive pushes the wrong row

I'm trying to use NavigationLink's isActive variable to pop back to the root view controller.
The problem I'm experiencing is that using isActive pushes the wrong row when clicking on a list item. Remove the isActive variable and everything works as expected.
Here's some example code for demonstration purposes:
struct ContentView: View {
#State private var activateNavigationLink: Bool = false
var exampleData = ["a", "b", "c"]
var body: some View {
NavigationView {
List(exampleData, id: \.self) { item in
NavigationLink(
destination: SecondView(item: item), isActive: $activateNavigationLink) {
Text(item)
}
}
}
}
}
SecondView
struct SecondView: View {
var item: String
var body: some View {
Text(item)
}
}
This is driving me nuts. Any help would be greatly appreciated.
Because activateNavigationLink is just a Bool in your code, if it is true, every NavigationLink will register as active in your List. Right now, this is manifesting as the last item (C) getting pushed each time.
Instead, you'd need some system to store which item is active and then translate that to a boolean binding for the NavigationLink to use.
Here's one possible solution:
struct ContentView: View {
#State private var activeNavigationLink: String? = nil
var exampleData = ["a", "b", "c"]
func bindingForItem(item: String) -> Binding<Bool> {
.init {
activeNavigationLink == item
} set: { newValue in
activeNavigationLink = newValue ? item : nil
}
}
var body: some View {
NavigationView {
List(exampleData, id: \.self) { item in
NavigationLink(
destination: SecondView(item: item), isActive: bindingForItem(item: item)) {
Text(item)
}
}
}
}
}
You should not use activeNavigationLink on main view it should be used with cellView
struct ContentView: View {
var exampleData = ["a", "b", "c"]
var body: some View {
NavigationView {
List(exampleData, id: \.self) { item in
CellView(item: item)
}
}
}
}
CellView
struct CellView: View {
#State private var activateNavigationLink: Bool = false
var item: String
var body: some View {
NavigationLink(
destination: SecondView(item: item), isActive: $activateNavigationLink) {
Text(item)
}
}
}
SecondView
struct SecondView: View {
var item: String
var body: some View {
Text(item)
}
}

How to pop NavigationLink back to ContentView when inside a loop

The answer here works tremendously but, as Im sure, is not ideal for everyone. Say ContentView2, in the link, is looping a list of NavigationLinks, when self.$isActive is true, it triggers all the NavigationLinks to open so not ideal. I've found out about using tag and selection.
// Inside a ForEach loop
// Omitted is the use of EnvironmentObject
NavigationLink(
destination: DestinationView(id: loop.id),
tag: loop.id,
selection: self.$routerState.selection,
label: {
NavCell(loopData: loop)
}
)
.isDetailLink(false)
// State:
class RouterState: ObservableObject {
//#Published var rootActive: Bool = false
#Published var tag: Int = 0
#Published var selection: Int? = nil
}
How to pop the NavigationLink when inside of a loop? The answer in the link works but not inside a loop. Is there a way to amend the answer to use both tag and selection?
Using example from link above:
import SwiftUI
struct ContentView: View {
#State var isActive : Bool = false
var body: some View {
NavigationView {
List {
/// Data from the loop will be passed to ContentView2
ForEach(0..<10, id: \.self) { num in
NavigationLink(
destination: ContentView2(rootIsActive: self.$isActive),
isActive: self.$isActive
) {
Text("Going to destination \(num)")
}
.isDetailLink(false)
}
}
}
}
}
struct ContentView2: View {
#Binding var rootIsActive : Bool
var body: some View {
NavigationLink(destination: ContentView3(shouldPopToRootView: self.$rootIsActive)) {
Text("Hello, World #2!")
}
.isDetailLink(false)
.navigationBarTitle("Two")
}
}
struct ContentView3: View {
#Binding var shouldPopToRootView : Bool
var body: some View {
VStack {
Text("Hello, World #3!")
Button (action: { self.shouldPopToRootView = false } ){
Text("Pop to root")
}
}.navigationBarTitle("Three")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Dynamic NavigationLink destination in SwiftUI?

I have a View 'B' that has an initialiser that takes an argument.
struct B: View {
let arg: Int
init(arg: Int) {
self.arg = arg
}
var body: some View {
Text("\(arg)")
}
}
And I have a Navigation View 'A'.
'A' has one button, which when pressed, shows a popup where the user picks a number from 1-5. A closure of type (Int) -> Void is called with the chosen number.
struct A: View {
#State var showPicker = false
var body: some View {
NavigationView {
Button(action: { self.showPicker = true }) {
Text("Pick Number")
}
.sheet(isPresented: self.$showPicker, content: {
NumberPicker { number in
*** Possible to navigate to B from here? ***
}
})
}
}
}
Question
Is it possible to initialise view B with the result from the closure, and then navigate to it?
This used to be possible with DynamicNavigationDestinationLink, however Apple deprecated it and stated in the release notes that NavigationLink contains its capabilities now. I have searched through the docs, however, I have not been able to figure out how to use NavigationLink to produce the same outcome.
You need to add a new view with another navigationview where you can pick the number.
Here's a full code example how i would approach this:
import SwiftUI
struct viewA: View {
var body: some View {
NavigationView {
NavigationLink(destination: viewC()) {
Text("Pick Number")
}
}
}
}
struct viewB: View {
#Binding var showSheet: Bool
#Binding var arg: Int
var body: some View {
NavigationView {
List {
ForEach((1...5), id: \.self) {number in
Button(action: {
self.arg = number
self.showSheet.toggle()
}) {
Text("\(number)")
}
}
}
}
}
}
struct viewC: View {
#State var showSheet:Bool = true
#State var arg: Int = 0
var body: some View {
NavigationView {
Text("\(arg)")
}
.sheet(isPresented: $showSheet) {
viewB(showSheet: self.$showSheet, arg: self.$arg)
}
}
}