SwiftUI Dynamic Tab View - swiftui

I trying to display a dynamic page tab view but it keep crash without the error code. The crashed happened when the number of tab view decrease. I cant find what's wrong with my code. So I tested in the sample project but it still the same. In the sample project when I clicked 3 and then clicked other numbers will got no problem, but when I clicked again 3 and scroll the tab view, it will crashed. Here is my sample project.
struct ContentView: View {
#ObservedObject var dataClass = DataClass()
var body: some View {
VStack(){
List{
ForEach(3..<6){ i in
Text("\(i)")
.onTapGesture {
self.dataClass.load(num: i)
}
}
}
if dataClass.data.count != 0{
TabView{
ForEach(dataClass.data.indices, id: \.self){ i in
Text(dataClass.data[i].alp)
}
}.tabViewStyle(PageTabViewStyle())
.padding(.horizontal)
.background(Color.red)
}
}
}
}
class DataClass : ObservableObject {
#Published var data : [DataStruct] = [DataStruct]()
init() {
}
func load(num : Int){
if num == 3 {
data = [DataStruct(num: 1, alp: "a"), DataStruct(num: 2, alp: "b"), DataStruct(num: 3, alp: "c")]
}
if num == 4 {
data = [DataStruct(num: 4, alp: "d"), DataStruct(num: 5, alp: "e"), DataStruct(num: 6, alp: "f"), DataStruct(num: 7, alp: "g")]
}
if num == 5 {
data = [DataStruct(num: 8, alp: "h"), DataStruct(num: 9, alp: "i"), DataStruct(num: 10, alp: "j"), DataStruct(num: 11, alp: "k")]
}
}
}
struct DataStruct {
var num : Int
var alp : String
}

Related

How do I have private state variables for a childview in swiftui?

So I am creating this app where a user chooses the multiplication table he wishes to practice on, and the number of questions on the first view, then, by pressing a button,
it goes on to the next view which passes this data, which will then create a bank of questions for him to do
There is an error saying i cannot pass a private variable, why is this so? I have attached my code for reference
import SwiftUI
struct ContentView: View {
#State private var multiplicationTable = 1
#State private var amountQuestions = 1
let multiplicationTables = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let amountQuestionss = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var body: some View {
NavigationView {
VStack {
Picker(selection: $multiplicationTable, label: Text("multiplicationTable")) {
ForEach(0 ..< 10) {num in
Text("\(multiplicationTables[num])")
}
}
.padding()
.padding()
Text("Choose number of Questions")
Picker("Select number of questions", selection: $amountQuestions) {
ForEach(0 ..< 10) {num in
Text("\(amountQuestionss[num])")
}
}
NavigationLink(destination: kek(One: multiplicationTable, Two: amountQuestions) .navigationBarHidden(true)) {
Button ("GO") {
}
.padding(50)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
}
.navigationTitle("Choose Multiplication Table")
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
this is the first view, here's the second view
import SwiftUI
struct kek: View {
let One : Int
let Two : Int
#State var Question : String
#State var answer = ""
#State var Three = 0
#State var Four = 0
func nextQuestion(){
Three = One
Four = Int.random(in: 0...10)
Question = "\(Three) * \(Four)"
}
var body: some View {
VStack {
Text("Question: What is \(Question)?")
Form {
Section {
TextField("Amount", text: $answer)
.keyboardType(.decimalPad)
}
}
Button ("Next") {
}
.padding(50)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
}
}
}
struct kek_Previews: PreviewProvider {
static var previews: some View {
kek(One: 1, Two: 2)
}
}
I'm not sure where you're getting the error as it is ok to pass a variable into another view. I modified your code to illustrate what I believe you're trying to achieve.
struct ContentView: View {
#State private var multiplicationTable = 1
#State private var amountQuestions = 1
let multiplicationTables = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let numberOfQuestions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var body: some View {
NavigationView {
VStack {
Picker("Multiplication Table", selection: $multiplicationTable) {
// There was an "off by one" issue in the kek View which I resolved by using the value of the array itself for the selection. Not the location in the array.
ForEach(multiplicationTables, id: \.self) { num in
Text("\(num)")
}
}
.pickerStyle(.segmented)
.padding([.top, .bottom], 20)
/*
Don't use
.padding()
.padding()
Instead you can specify how large you'd like the padding to be around the view.
*/
Text("Choose number of Questions")
Picker("Select number of questions", selection: $amountQuestions) {
ForEach(0 ..< numberOfQuestions.count) {num in
Text("\(numberOfQuestions[num])")
}
}
NavigationLink {
kek(one: multiplicationTable, two: amountQuestions)
} label: {
// A NavigationLink is clickable by default, so you don't need to place a button inside of it.
// Here I used Text and formatted it just as you had with your Button.
Text("Go")
.padding(50)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
}
.navigationTitle("Choose Multiplication Table")
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
struct kek: View {
let one: Int
let two: Int // note: It doesn't look like you're actually using this property. Consider removing it.
#State private var answer = ""
#State private var randomNumber = 0
#State private var three = 0
#State private var question = ""
func nextQuestion(){
three = one
randomNumber = Int.random(in: 0...10)
question = "\(three) * \(randomNumber)"
}
var body: some View {
VStack {
Text("Question: What is \(question)?")
Form {
Section {
TextField("Amount", text: $answer)
.keyboardType(.decimalPad)
}
}
Button ("Next") {
nextQuestion()
}
.padding(50)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
}
}
}

Double tap gesture fail to update

I am trying to make a popup menu (favorite menu), and I am stuck when I try to update my detail menu popup screen.
Here, when I double-tapped one of my Home() list objects, the popup menu is popup. From that popup menu, the user will select one of the two buttons. However, every time I tap on the list object, the popup menu shows only the first name of the first array. It looks like the Double Tap Gesture is not updated. It stuck in the first array although I tried to tap one a different list object. Please help me how to update my popup screen based on the selected list object. Also, NavigationLink might be the solution, but I don't want to use NavigationLink. I just want to toggle the screen.
All the detailed code examples are as below.
This is my sample array:
import SwiftUI
struct DataArray: Identifiable {
let id = UUID()
let number: Int
let cities: String
var name1: String?
var name2: String?
var name3: String?
var name4: String?
}
public struct ListDataArray {
static var dot = [
DataArray(number: 1,
cities: "Baltimore"
name1: "A",
name2: "B"),
DataArray(number: 2,
cities: "Frederick"),
DataArray(number: 3,
cities: "Catonsville"
name1: "Aa",
name2: "Bb",
name3: "Cc",
name4: "Dd"),
]
}
This is for my Double Tab Gesture:
struct Home: View {
#EnvironmentObject var tap: Tapping
var datas: [DataArray] = ListDataArray.dot
var body: some View {
ScrollView {
LazyVStack(spacing: 10) {
ForEach (data, id: \.id) { data in
if let data1 = data.name1 {
Text(data1)
}
if let city1 = data.cities {
Text(city1)
}
if let data2 = data.name2 {
Text(data2)
}
}
.onTapGesture (count: 2) {
self.tap.isDoubleTab = true
}
}
}
}
}
Finally, this is my target popup menu:
struct DetailMenu: View {
var dataArr = DataArray
#EnvironmentObject var tap: Tapping
var body: some View {
VStact {
Text(lyric.name1)
HStack {
Button(action: {
tap.isDoubleTab = false
}, label: {
Image(systemName: "suit.heart")
})
Button(action: {
tap.isDoubleTab = false
}, label: {
Image(systemName: "gear")
})
}
}
}
}
struct ContenView: View {
var body: some View {
ZStack {
if tap.isDoubleTab {
DetailMenu(dataArr: ListDataArray.dot[0])
} else {
AlphaHome() // This is just another struct
}
}
}
}

swiftui+combine: why isFavoriteO changed when scroll the LazyVGrid?

I have a LazyVGrid, every item with is favorite button. and use combine to debounce user input($isFavoriteI), when isFavoriteO changed, then modify the items.
it works fine, but when i scroll the list, log will print: "X, isFavorite changed as false/true)", what cause isFavoriteO changed and why? because of item reusing in list? how to avoid it?
index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true
index 7, isFavorite changed as true
import SwiftUI
import Combine
struct Item {
var index: Int
var favorite: Bool
}
var items = [
Item(index: 0, favorite: true),
Item(index: 1, favorite: false),
Item(index: 2, favorite: true),
Item(index: 3, favorite: false),
Item(index: 4, favorite: true),
Item(index: 5, favorite: false),
Item(index: 6, favorite: true),
Item(index: 7, favorite: false),
// Item(index: 8, favorite: true),
// Item(index: 9, favorite: false),
// Item(index: 10, favorite: true),
// Item(index: 11, favorite: false),
// Item(index: 12, favorite: true),
// Item(index: 13, favorite: false),
// Item(index: 14, favorite: true),
// Item(index: 15, favorite: false),
// Item(index: 16, favorite: true),
// Item(index: 17, favorite: false),
// Item(index: 18, favorite: true),
// Item(index: 19, favorite: false),
]
struct ViewModelInListTestView: View {
var body: some View {
ScrollView(showsIndicators: false) {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200), spacing: 4, alignment: .center)], spacing: 4) {
ForEach(items, id: \.index) { item in
ItemView(item: item)
}
}
}.navigationTitle("ViewModel In List")
}
}
struct ItemView: View {
let item: Item
#ObservedObject var viewModel: ViewModel
init(item: Item) {
print("ItemView.init, \(item.index)")
self.item = item
self.viewModel = ViewModel(item: item)
}
var body: some View {
HStack {
Text("index \(item.index)")
Spacer()
Image(systemName: viewModel.isFavoriteI ? "heart.fill" : "heart")
.foregroundColor(viewModel.isFavoriteI ? .red : .white)
.padding()
.onTapGesture { onFavoriteTapped() }
.onChange(of: viewModel.isFavoriteO) { isFavorite in
setFavorite(isFavorite)
}
}
.frame(width: 200, height: 150)
.background(Color.gray)
}
func onFavoriteTapped() {
viewModel.isFavoriteI.toggle()
}
func setFavorite(_ isFavorite: Bool) {
print("index \(item.index), isFavorite changed as \(isFavorite)")
items[item.index].favorite = isFavorite
}
class ViewModel: ObservableObject {
#Published var isFavoriteI: Bool = false
#Published var isFavoriteO: Bool = false
private var subscriptions: Set<AnyCancellable> = []
init(item: Item) {
print("ViewModel.init, \(item.index)")
let isFavorite = item.favorite
isFavoriteI = isFavorite; isFavoriteO = isFavorite
$isFavoriteI
.print("index \(item.index) isFavoriteI:")
.dropFirst()
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
.removeDuplicates()
.eraseToAnyPublisher()
.print("index \(item.index) isFavoriteO:")
.receive(on: DispatchQueue.main)
.assign(to: \.isFavoriteO, on: self)
.store(in: &subscriptions)
}
}
}
update # 4.15
according to #Cenk Bilgen, i re-write the code, but strange thing happened. print("set favorite as (favorite)") will not present if adding removeDuplicates. why?
import SwiftUI
import Combine
struct Item: Identifiable {
var index: Int
var favorite: Bool
var id: Int { index }
}
class Model: ObservableObject {
#Published var items = [
Item(index: 0, favorite: true),
Item(index: 1, favorite: false),
Item(index: 2, favorite: true),
Item(index: 3, favorite: false),
Item(index: 4, favorite: true),
Item(index: 5, favorite: false),
Item(index: 6, favorite: true),
Item(index: 7, favorite: false),
]
}
struct ViewModelInListTestView: View {
#StateObject var model = Model()
var body: some View {
print("ViewModelInListTestView refreshing"); return
ScrollView(showsIndicators: false) {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200), spacing: 4, alignment: .center)], spacing: 4) {
ForEach(model.items.indices) { index in
ItemView(item: model.items[index])
.environmentObject(model)
}
}
}.navigationTitle("ViewModel In List")
}
struct ItemView: View {
#EnvironmentObject var model: Model
let item: Item
#State private var updateFavourite = PassthroughSubject<Bool, Never>()
#State private var favorite: Bool = false
init(item: Item) {
self.item = item
self._favorite = State(initialValue: item.favorite)
}
var body: some View {
print("ItemView \(item.index) refreshing"); return
HStack {
Text("index \(item.index)")
Spacer()
Image(systemName: favorite ? "heart.fill" : "heart")
.foregroundColor(favorite ? .red : .white)
.padding()
.onTapGesture {
favorite.toggle()
updateFavourite.send(favorite)
}
.onReceive(
updateFavourite
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
// .removeDuplicates() <------ HERE
// .eraseToAnyPublisher()
) { favorite in
print("set favorite as \(favorite)")
model.items[item.index].favorite = favorite
}
}
.frame(width: 200, height: 150)
.background(Color.gray)
}
}
}
struct Item: Identifiable {
var index: Int
var favorite: Bool
var id: Int { index }
}
class Model: ObservableObject {
#Published var items = [
Item(index: 0, favorite: true),
Item(index: 1, favorite: false),
Item(index: 2, favorite: true),
Item(index: 3, favorite: false),
Item(index: 4, favorite: true),
Item(index: 5, favorite: false),
Item(index: 6, favorite: true),
Item(index: 7, favorite: false),
]
}
struct SimplerView: View {
#StateObject var model = Model()
var body: some View {
ScrollView(showsIndicators: false) {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200), spacing: 4, alignment: .center)], spacing: 4) {
ForEach(items.indices) { index in
ItemView(item: $model.items[index])
.environmentObject(model)
}
}
}.navigationTitle("ViewModel In List")
}
struct ItemView: View {
#EnvironmentObject var model: Model
#Binding var item: Item
#State private var updateFavourite = PassthroughSubject<Bool, Never>()
var body: some View {
HStack {
Text("index \(item.index)")
Spacer()
Image(systemName: item.favorite ? "heart.fill" : "heart")
.foregroundColor(item.favorite ? .red : .white)
.padding()
.onTapGesture {
updateFavourite.send(item.favorite)
}
.onReceive(updateFavourite
.debounce(for: .seconds(0.5), scheduler: RunLoop.main)) { value in
item.favorite = !value
}
}
.frame(width: 200, height: 150)
.background(Color.gray)
}
}
}
I don't really understand how isFavoriteI and isFavoriteO work, but you
could try this:
in ItemView remove the
.onChange(of: viewModel.isFavoriteO) { isFavorite in
setFavorite(isFavorite)
}
and change:
func onFavoriteTapped() {
viewModel.isFavoriteI.toggle()
print("\(item.index), isFavorite changed as \(viewModel.isFavoriteI)")
items[item.index].favorite = viewModel.isFavoriteI
}
The best of doing this is in this way in down code, SwiftUI would stop unnecessary render, and it will render if it needs!
You had some issue that you should use id for your items, plus combine does not work well in this case, so use better and easier way in down:
import SwiftUI
struct ContentView: View {
#StateObject var itemModel: ItemModel = sharedItemModel
var body: some View {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200), spacing: 4, alignment: .center)], spacing: 4) {
ForEach(Array(itemModel.items.enumerated()), id:\.element.id) { (offset, element) in
ItemView(index: offset, favorite: element.favorite)
}
}
}
Button("append new random element") { itemModel.items.append(Item(favorite: Bool.random())) }
.padding()
}
}
struct ItemView: View {
let index: Int
let favorite: Bool
init(index: Int, favorite: Bool) {
self.index = index
self.favorite = favorite
}
var body: some View {
print("rendering item: " + index.description)
return HStack {
Text("index " + index.description)
.bold()
.padding()
Spacer()
Image(systemName: favorite ? "heart.fill" : "heart")
.foregroundColor(Color.red)
.padding()
.onTapGesture { sharedItemModel.items[index].favorite.toggle() }
}
.frame(width: 200, height: 150)
.background(Color.gray)
.cornerRadius(10.0)
}
}
struct Item: Identifiable {
let id: UUID = UUID()
var favorite: Bool
}
class ItemModel: ObservableObject {
#Published var items: [Item] = [Item]()
}
let sharedItemModel: ItemModel = ItemModel()

SwiftUI update observedObject in Cell View of list

I tried to change the value of the observedObject. The value will changed but not permanently. When I scroll down and scroll back, the value will changed back to original value. I guess is the problem of #State in the cell view of list. But I cant figure out what the solution to change the data permanently in observedObject. So what should I do to change the data permanently by using the data cell.
struct ContentView: View {
#ObservedObject var dataClass = DataClass()
var body: some View {
List(dataClass.data){ item in
DataCell(item: item)
}
}
}
struct DataCell: View {
#State var item : DataModal
var body: some View{
HStack{
Text("\(item.num!)")
Text("\(item.val!)").onTapGesture {
item.val = 1
}
}
.padding(.vertical, 100)
}
}
struct DataModal : Identifiable {
var id = UUID()
var num : Int?
var val : Int?
}
class DataClass : ObservableObject {
#Published var data = [DataModal]()
init(){
load()
}
func load(){
data = [DataModal(num: 1, val: 1),DataModal(num: 2, val: 2),DataModal(num: 3, val: 3),DataModal(num: 4, val: 4),DataModal(num: 5, val: 5),DataModal(num: 6, val: 6),DataModal(num: 7, val: 7),DataModal(num: 8, val: 8),DataModal(num: 9, val: 9),DataModal(num: 10, val: 10),DataModal(num: 11, val: 11),DataModal(num: 12, val: 12),DataModal(num: 13, val: 13)]
}
}
You pass a copy of DataModal, because it is a value type. In this scenario you need to pass binding to original value inside dataClass, like
struct ContentView: View {
#ObservedObject var dataClass = DataClass()
var body: some View {
List(dataClass.data.indices, id: \.self){ index in
DataCell(item: $dataClass.data[index])
}
}
}
struct DataCell: View {
#Binding var item : DataModal
var body: some View{
HStack{
Text("\(item.num!)")
Text("\(item.val!)").onTapGesture {
item.val = 1
}
}
.padding(.vertical, 100)
}
}

SwiftUI How to Create a button that adds an item to a list that contains a navigation link

I've never done a post on this before, so hopefully this is set up correctly.
I'm new to swift, and I want to create a button that adds a new item to a list that contains a navigation link to a file that it would create with linked data from the previous item, and I haven't found any way to do this after spending a few days on research and testing.
This is what my app currently looks like for the layout I want in the end: Q1 , and here is a preview of the different Q1-4 Views I mentioned: Q1-4
I know it's a lot, so let me explain more in depth: I want to have a list contained in what is called 'Q1' (as seen above) that starts out with 'Week 1', and as you click the add button, I want it to add a 'Week 2' and so forth up to 10 weeks. Once you hit 10 weeks, I want the user to have to change to the different view, 'Q2', which then they can add Week 11-20, and so forth until Q4, which limits it to a total of 40 Weeks. I want each week to contain a navigation link to a new view; however, I also want data from the previous week to be carried over as soon as I create the new week, so the user won't have to manually put in the previous week's data.
I know how to do some of this by using a JSON file for the numbers, as I've seen tutorials on that, however, I don't see a point to this, as the only data I need for the Week numbers are 1-40, but I can't seem to get it to work with an array or anything. I do know that I can use an #EnvironmentObject to get the data I need from the other pages, but I'm not exactly sure how to set that up either. Other than that, I'm stuck! Here is my code:
import SwiftUI
struct BillsView: View {
#State private var quarterNumber = 0
let quarterNumbers = [1, 2, 3, 4]
var body: some View {
NavigationView{
VStack {
Section {
Picker("Quarter Number", selection: $quarterNumber) {
ForEach(0 ..< quarterNumbers.count) {
Text("Q\(self.quarterNumbers[$0])")
}
}
.pickerStyle(SegmentedPickerStyle())
.padding(.horizontal)
if quarterNumber == 0 {
Q1View()
} else if quarterNumber == 1 {
Q2View()
} else if quarterNumber == 2 {
Q3View()
} else if quarterNumber == 3 {
Q4View()
}
}
Spacer()
}
.navigationBarTitle("Bills")
.navigationBarItems(leading: EditButton(),
trailing: Button(action: {
//Adds the new week
}){
Image(systemName: "plus.circle.fill")
})
}
}
}
struct Q1View: View {
#State private var weekNumber = 0
let weekNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var body: some View {
List {
NavigationLink(destination: Week1View()) {
Text("Week 1")
}
}
}
}
struct Week1View: View {
var body: some View {
List {
link(label: "Gross Income", destination: GrossIncome())
link(label: "Expenses", destination: Expenses())
}.navigationBarTitle(Text("Week 1"), displayMode: .inline)
}
private func link<Destination: View>(label: String, destination: Destination) -> some View {
return NavigationLink(destination: destination) {
Text(label)
}
}
}
I am not quite sure whether i understood you right, but i made a very simple example which answers the question of your title
import SwiftUI
struct ContentView: View {
#State private var list : [String] = ["Chris"]
#State private var quarterNumber = 0
var body: some View {
Group () {
Button(action: {
self.list.append("whatever")
}) {
Text("tap me")
}
NavigationView{
List(list, id: \.self) { item in
NavigationLink(
destination: View2(text: "hallo")
.navigationBarTitle(Text("Categories"), displayMode: .automatic)
) {
Text("Categories")
}.isDetailLink(false) // damit der Doof nicht rechts das nächste Menu öffnet
}
}
}
}
}
struct View2: View {
var text : String
var body: some View {
Text(text)
}
}