I'm trying to update the list from view model when the app is starting. But it only updates when items are added or removed. How to fix it??
import SwiftUI
import UserNotifications
var center = UNUserNotificationCenter.current()
struct ContentView: View {
#ObservedObject var vm = ViewModel()
var body: some View {
VStack (alignment: .center, spacing: 0.3) {
List {
ForEach(0..<vm.list.count, id: \.self) { index in
NotificationCellView(
index: index,
vm: vm)
}
.onDelete { vm.del($0) }
}
.onAppear{
vm.getNotifications()
requestNotification()
}}}
}
View model class. I'am appending the list with UNNotificationRequest elements :
class ViewModel: ObservableObject {
#Published var list: [UNNotificationRequest] = []
func getNotifications() {
center.getPendingNotificationRequests { requests in
for request in requests {
DispatchQueue.main.async {
self.list.append(request)
}
}
}
}
}
Related
I'm trying to find the reason why .listRowBackground is not updated when a new item has been added to the list. Here is my code sample:
#main
struct BGtestApp: App {
#ObservedObject var viewModel = ViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(viewModel)
}
}
}
struct ContentView: View {
#EnvironmentObject var vm: ViewModel
var body: some View {
NavigationView {
List {
ForEach(vm.items, id: \.self) { item in
NavigationLink {
DetailView().environmentObject(vm)
} label: {
Text(item)
}
.listRowBackground(Color.yellow)
}
}
}
}
}
struct DetailView: View {
#EnvironmentObject var vm: ViewModel
var body: some View {
Button("Add new") {
vm.items.append("Ananas")
}
}
}
How it looks like:
TIA for you help!
You can force the list to refresh when you cone back to the list. You can tag an id for your list by using .id(). Here is my solution:
struct ContentView: View {
#EnvironmentObject var vm: ViewModel
#State private var viewID = UUID()
var body: some View {
NavigationView {
List {
ForEach(vm.items, id: \.self) { item in
NavigationLink {
DetailView()
.environmentObject(vm)
} label: {
Text(item)
}
}
.listRowBackground(Color.yellow)
}
.id(viewID)
.onAppear {
viewID = UUID()
}
}
}
}
Hope it's helpful for you.
The solution I found is not ideal, but should work. What I did is made items to be #State variable :
struct ContentView: View {
#EnvironmentObject var vm: ViewModel
#State var items: [String] = []
var body: some View {
NavigationView {
VStack {
List {
ForEach(items, id: \.self) { item in
NavigationLink {
DetailView().environmentObject(vm)
} label: {
RowView(item: item)
}
.listRowBackground(Color.yellow)
}
}
.onAppear {
self.items = vm.items
}
}
}
So I am working on a list view, where tapping an item on the list opens the detail view for that item.
I also want to have a button which adds an item to the list, and immediately opens the detail view.
Something like this:
struct Item: Identifiable {
let id: UUID
init() {
self.id = UUID()
}
}
struct DetailView: View {
let item: UUID
}
struct ContainerView: View {
#State var items: [Item] = []
var body: some View {
VStack {
List {
ForEach(items) { item in
NavigationLink(
"Item: \(item.id)",
destination: DetailView(item:item)
)
}
}
Button("New Item") {
let newItem = Item()
items += [newItem]
// now I want to go to DetailView(item:newItem)
// how do I set the navigation link target here?
}
}
}
}
How can I achieve this?
I see there is this method for programmatic navigation:
NavigationItem.init<S, V>(S, tag: V, selection: Binding<V?>, destination: () -> Destination)
But I think this will not work as the tag is not known ahead of time.
You almost got it, remember that a NavigationLink can only "navigate" inside NavigationView
import Foundation
import SwiftUI
struct Item: Identifiable {
let id: UUID
init() {
self.id = UUID()
}
}
struct DetailView: View {
let item: UUID
var body: some View {
Text("I'm the item \(item)")
}
}
struct ContainerView: View {
#State var items: [Item] = []
#State var activeItem: UUID?
var body: some View {
NavigationView{
VStack {
List {
ForEach(items) { item in
NavigationLink(
destination: DetailView(item: item.id),
tag: item.id,
selection: $activeItem
){
Text("Item: \(item.id)")
}
}
}
Button("New Item") {
let newItem = Item()
items += [newItem]
self.activeItem = newItem.id
// now I want to go to DetailView(item:newItem)
// how do I set the navigation link target here?
}
}
.navigationTitle("Main View")
}
}
}
I have multiple views created by a ForEACH. Each View has a textfield where a user can enter a number. I would like to subtotal each entry in each view. In other words subtotal the binding in each view.
Is my approach wrong?
ForEach(someArray.allCases, id: \.id) { item in
CustomeRowView(name: item.rawValue)
}
struct CustomeRowView: View {
var name: String
#State private var amount: String = ""
var body: some View {
VStack {
HStack {
Label(name, systemImage: image)
VStack {
TextField("Amount", text: $amount)
.frame(width: UIScreen.main.bounds.width / 7)
}
}
}
}
}
Any help would be greatly appreciated.
there are many ways to achieve what you ask. I present here a very
simple approach, using an ObservableObject to keep the info in one place.
It has a function to add to the info dictionary fruits.
A #StateObject is created in ContentView to keep one single source of truth.
It is passed to the CustomeRowView view using #ObservedObject, and used to tally
the input of the TextField when the return key is pressed (.onSubmit).
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class FruitCake: ObservableObject {
#Published var fruits: [String : Int] = ["apples":0,"oranges":0,"bananas":0]
// adjust for you purpose
func add(to name: String, amount: Int) {
if let k = fruits.keys.first(where: {$0 == name}),
let sum = fruits[k] {
fruits[k] = sum + amount
}
}
}
struct ContentView: View {
#StateObject var fruitCake = FruitCake()
var body: some View {
VStack {
ForEach(Array(fruitCake.fruits.keys), id: \.self) { item in
CustomeRowView(name: item, fruitCake: fruitCake)
}
}
}
}
struct CustomeRowView: View {
let name: String
#ObservedObject var fruitCake: FruitCake
#State private var amount = 0
var body: some View {
HStack {
Label(name, systemImage: "info")
TextField("Amount", value: $amount, format: .number)
.frame(width: UIScreen.main.bounds.width / 7)
.border(.red)
.onSubmit {
fruitCake.add(to: name, amount: amount)
}
// subtotal
Text("\(fruitCake.fruits[name] ?? 0)")
}
}
}
I came across a situation that you use class data as your data source, and display them in a swiftUI list view, when you update your data source, the swiftUI list view won't be updated, what can we do to make the class data updates interactive with swiftUI?
see code blow:
I define the environment object :
import Foundation
import Combine
class DataSource: ObservableObject {
public static let shared = DataSource()
#Published var datalist: [RowData] = []
func fetch() -> Void {
for n in 1...50 {
let data = RowData(title: "Index:\(n)", count: 0)
datalist.insert(data, at: 0)
}
}
func update() {
for data in datalist {
data.count = data.count+1
print("\(data.title) update count to :\(data.count)")
data.objectWillChange.send()
}
self.objectWillChange.send()
}
}
to display each data in a Row View:
import SwiftUI
struct RowView: View {
#State var data: RowData
var body: some View {
HStack{
Text(data.title)
Spacer()
Text("\(data.count)")
}.padding()
}
}
struct RowView_Previews: PreviewProvider {
static var previews: some View {
RowView(data: RowData(title: "text", count: 1))
}
}
class RowData: ObservableObject {
var title: String = ""
var count: Int = 0
init(title: String, count: Int) {
self.title = title
self.count = count
}
}
in content view, display the data in a list view, I would like to refresh all the view updates when click update button. the button triggers the update methods to update the class data value from data source.
struct ContentView: View {
#EnvironmentObject var data: DataSource
#State var shouldUpdate:Bool = false
#State var localData:[RowData] = []
var body: some View {
VStack {
Button(action: {
// your action here
self.data.update()
self.shouldUpdate.toggle()
self.localData.removeAll()
self.localData = self.data.datalist
}) {
Text("update")
}
List {
ForEach(0..<self.localData.count, id:\.self) { index in
RowView(data: self.localData[index])
}
}
}
}
}
Well... I don't see the reason to have localData, but, anyway, here is modified code that works.
Tested with Xcode 12 / iOS 14
class DataSource: ObservableObject {
public static let shared = DataSource()
#Published var datalist: [RowData] = []
func fetch() -> Void {
for n in 1...50 {
let data = RowData(title: "Index:\(n)", count: 0)
datalist.insert(data, at: 0)
}
}
func update() {
for data in datalist {
data.count = data.count+1
print("\(data.title) update count to :\(data.count)")
}
self.objectWillChange.send()
}
}
struct RowView: View {
#ObservedObject var data: RowData
var body: some View {
HStack{
Text(data.title)
Spacer()
Text("\(data.count)")
}.padding()
}
}
class RowData: ObservableObject {
#Published var title: String = ""
#Published var count: Int = 0
init(title: String, count: Int) {
self.title = title
self.count = count
}
}
struct ContentView: View {
#EnvironmentObject var data: DataSource
#State var localData:[RowData] = []
var body: some View {
VStack {
Button(action: {
// your action here
self.data.update()
self.localData = self.data.datalist
}) {
Text("update")
}
List {
ForEach(0..<self.localData.count, id:\.self) { index in
RowView(data: self.localData[index])
}
}
}
.onAppear {
self.data.fetch()
self.localData = self.data.datalist
}
}
}
I have a master detail view that navigates a series of sequential lists using totally vanilla SwiftUI and I'm noticing two things.
The list rows width jitters when you access one via navigation link?
The .navigationBarTitle isn't visible until the new view is completely on screen?
Seems like neither of these things are suppose to happen? Wonder if anyone has any ideas (video and code attached).
Video of the issues: https://www.dropbox.com/s/5jq3e8chay6hsy5/jitter.mov?dl=0
UserList.swift:
import SwiftUI
struct UserList: View {
var body: some View {
NavigationView {
List(userData) { this in
NavigationLink(destination: CityList(user: this, cities: this.cities)) {
UserRow(user: this)
}
}
.navigationBarTitle(Text("Users"))
}
}
}
UserRow.swift:
import SwiftUI
struct UserRow: View {
var user: UserModel
var body: some View {
VStack(alignment: .leading) {
Text(user.firstName + " " + user.lastName)
.font(.headline)
}
}
}
CityList.swift:
import SwiftUI
struct CityList: View {
var user: UserModel
var cities: [CityModel]
var body: some View {
List (cities) { this in
NavigationLink(destination: TownList(city: this, towns: this.towns)) {
CityRow(city: this)
}.navigationBarTitle(Text(self.user.firstName + self.user.lastName))
}
}
}
CityRow.swift:
import SwiftUI
struct CityRow: View {
var city: CityModel
var body: some View {
VStack(alignment: .leading) {
Text(city.name)
.font(.headline)
}
}
}