How we can use ObservableObject in onChange/onReceive? - swiftui

I have this code, which has a struct and ObservableObject. I am feeding my ObservableObject with some Buttons.
I am trying get run some print in any changes of my ObservableObject, it should be working, but i do not know what I am missing.
PS: Also I want to know should I use .onChange or .onReceive for this work?
struct Data: Identifiable
{
let id = UUID()
var name: String
}
let dataTrain1 = [Data(name: "A"), Data(name: "A"), Data(name: "A"), Data(name: "A"), Data(name: "A")]
let dataTrain2 = [Data(name: "A"), Data(name: "B"), Data(name: "B"), Data(name: "A"), Data(name: "A")]
class DataModel: ObservableObject
{
#Published var items: [Data] = []
}
struct ContentView: View
{
#StateObject var dataModel = DataModel()
var body: some View
{
ZStack
{
VStack
{
HStack
{
Button("Load dataTrain1") { dataModel.items = dataTrain1 }
Spacer()
Button("Load dataTrain2") { dataModel.items = dataTrain2 }
}
.padding(.horizontal)
Spacer()
}
HStack
{
ForEach(dataModel.items) { item in
Text(item.name)
.font(.title)
.bold()
.foregroundColor(item.name == "A" ? Color.red : Color.blue)
}
}
}
.onChange(of: dataModel.items) { _ in print("dataModel Changed!") } // ← Here
.onReceive(dataModel.$items) { _ in print("dataModel Changed!") } // ← Here
}
}

To use onChange your Data must conform to Equatable.
struct Data: Identifiable, Equatable { ... }

Related

Picker view is not changing when you try to select a different option in swiftui

I am using in swiftUI. When select picker, it is not changing. Here is code..
Here is datamodel:
struct SourceAccountModel:Codable,Identifiable{
var id: Int
let accountNumber: String
let accountTitle: String
let priaryAccount: String
init(id:Int=0,accountNumber: String, accountTitle: String, priaryAccount: String) {
self.id = id
self.accountNumber = accountNumber
self.accountTitle = accountTitle
self.priaryAccount = priaryAccount
}
}
Here is my code
struct Test2: View {
#State private var selectedOption = "Option 1"
#State private var sourceAccountList = [SourceAccountModel]()
var body: some View {
VStack{
ZStack {
RoundedRectangle(cornerRadius: 8)
.fill(Color.white)
.shadow(radius: 2)
Picker(selection: $selectedOption,label: EmptyView()) {
ForEach (0..<sourceAccountList.count,id: \.self) {
Text(sourceAccountList[$0].accountNumber)
}
}
.padding(8)
}
.frame(maxWidth: .infinity)
}.onAppear{
intitializeValue()
}
}
func intitializeValue(){
self.sourceAccountList.append(SourceAccountModel(id:1,accountNumber: "Option 1", accountTitle: "", priaryAccount: ""))
self.sourceAccountList.append(SourceAccountModel(id:2,accountNumber: "Option 2", accountTitle: "", priaryAccount: ""))
}
}
Always select first value. What is the wrong with my code?
selectedOption is a String, but your ForEach iterates over Range<Int>.
You can fix this by changing selectedOption to Int, e.g.
#State private var selectedOption = 0
You might find it easier to store the actual object in selectedOption: SourceAccountModel, iterate over the sourceAccountList, and tag each row:
struct SourceAccountModel: Identifiable, Hashable {
let id: Int
let accountNumber: String
init(id: Int, accountNumber: String) {
self.id = id
self.accountNumber = accountNumber
}
}
struct ContentView: View {
init() {
let sourceAccountList = [SourceAccountModel(id: 1, accountNumber: "Option 1"),
SourceAccountModel(id: 2, accountNumber: "Option 2")]
_sourceAccountList = State(wrappedValue: sourceAccountList)
_selectedOption = State(wrappedValue: sourceAccountList[0])
}
#State private var selectedOption: SourceAccountModel
#State private var sourceAccountList = [SourceAccountModel]()
var body: some View {
VStack {
Picker("Select", selection: $selectedOption) {
ForEach(sourceAccountList) { model in
Text(model.accountNumber).tag(model)
}
}
}
}
}

Swiftui Mapping Nested JSON to Flat Model

I need to access the data in the nested dictionary of the Memodel struct. From both the music and image dictionary. Please any help is needed to map out correctly, i have tried using AzampSharp's example https://www.youtube.com/watch?v=b5wVIQNrI6k but i believe i am doing something wrong. Thanks.
import SwiftUI
struct MemodelAPIResult: Codable {
let data: [Memodel]
enum CodingKeys: String, CodingKey {
case data = "results"
}
}
struct Memodel: Identifiable, Codable {
var id: String
var followers: String
var following: String
let music: [MemodelMusic]
let images: [MemodelImages]
}
struct MemodelMusic: Identifiable, Codable, Hashable {
var id: String
var musicfile: URL
var musicname: String
var musicartistname: String
var musicgenre: String
}
struct MemodelImages: Identifiable, Codable, Hashable {
var id: String
var albumimages: URL
var abumlikes: String
var albumviews: String
}
Below is my ObservableObject in my View Model
import Foundation
import SwiftUI
import Combine
import CryptoKit
class MeViewmodel: ObservableObject {
#Published var me: [Memodel]? = nil
init() {
self.fetchme()
}
func fetchme() {
let url = ""
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!) { (data, _, err) in
if let error = err{
print(error.localizedDescription)
return
}
guard let APIData = data else {
print("No Data found")
return
}
do {
let new = try JSONDecoder().decode(MemodelAPIResult.self, from: APIData)
DispatchQueue.main.async {
self.me = new.data
}
}
catch{
print(error)
}
}
.resume()
}
}
And then the item view
struct MeMusicItemView: View {
//E-MARK: - Properties
var me: Memodel
//E-MARK: - Body
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 5) {
Text(me.music[0].musicname)
.font(.callout)
.fontWeight(.medium)
.foregroundColor(.white)
Text(me.music[0].musicartistname)
.font(.caption2)
.fontWeight(.light)
.foregroundColor(.white)
Text(me.music[0].musicgenre)
.font(.system(size: 8))
.fontWeight(.light)
.foregroundColor(.gray)
}
}
}
}
And also the ForEach in the parent View....
if let meMusicData = meMusicData.mememe {
ForEach(meMusicData) { music in
MeMusicItemView(memusic: music)
}
} else {
ProgressView()
.padding(.top, 20)
}
There is not enough info for me to really understand what you are doing, but
here is some code you can have a look at and recycle for your purpose:
struct ContentView: View {
#StateObject var viewModel = MeViewmodel() // <-- here your model
var body: some View {
List {
ForEach(viewModel.me) { memod in // <-- loop over the Memodel array
ForEach(memod.music) { music in // <-- loop over the MemodelMusic array
MeMusicItemView(memusic: music) // <-- display 1 MemodelMusic
}
}
}
}
}
struct MeMusicItemView: View {
//E-MARK: - Properties
#State var memusic: MemodelMusic // <-- here
//E-MARK: - Body
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 5) {
Text(memusic.musicname)
.font(.callout)
.fontWeight(.medium)
.foregroundColor(.pink)
Text(memusic.musicartistname)
.font(.caption2)
.fontWeight(.light)
.foregroundColor(.green)
Text(memusic.musicgenre)
.font(.system(size: 8))
.fontWeight(.light)
.foregroundColor(.blue)
}
}
}
}
class MeViewmodel: ObservableObject {
#Published var me: [Memodel] = [] // <--- here no optional, it is easier to deal with
init() {
self.fetchme()
}
func fetchme() {
// ......
}
}
struct Memodel: Identifiable, Codable {
var id: String
var followers: String
var following: String
let music: [MemodelMusic]
let images: [MemodelImages]
}
struct MemodelMusic: Identifiable, Codable, Hashable {
var id: String
var musicfile: URL
var musicname: String
var musicartistname: String
var musicgenre: String
}
struct MemodelImages: Identifiable, Codable, Hashable {
var id: String
var albumimages: URL
var abumlikes: String
var albumviews: String
}

SwiftUI - Update data on Firebase's Realtime database

I have successfully displayed the data to the UI, but I want the user to be able to update my data again when tapping the "Save" button . Hope you can help me!
Profile
I have successfully displayed the data to the UI, but I want the user to be able to update my data again when tapping the "Save" button . Hope you can help me!
There are many ways to achieve what you want. This is just one approach, by
passing the profileViewModel to EditProfile:
class ProfileViewModel: ObservableObject {
#Published var user = Profile(id: "", image: "", birthDay: "", role: [], gender: "", name: "")
private var ref: DatabaseReference = Database.database().reference()
func fetchData(userId: String? = nil) {
// 8hOqqnFlfGZTj1u5tCkTdxAED2I3
ref.child("users").child(userId ?? "default").observe(.value) { [weak self] (snapshot) in
guard let self = self,
let value = snapshot.value else { return }
do {
print("user: \(value)")
self.user = try FirebaseDecoder().decode(Profile.self, from: value)
} catch let error {
print(error)
}
}
}
func saveUser() {
// save the user using your ref DatabaseReference
// using setValue, or updateChildValues
// see https://firebase.google.com/docs/database/ios/read-and-write
}
}
struct EditProfile: View {
#ObservedObject var profileViewModel: ProfileViewModel // <--- here
var body: some View {
VStack {
Text(profileViewModel.user.name) // <--- you probably meant TextField
.font(.custom("Poppins-Regular", size: 15))
.foregroundColor(Color.black)
Text("\(profileViewModel.user.birthDay)!")
.font(.custom("Poppins-Regular", size: 22))
.fontWeight(.bold)
.foregroundColor(Color.black)
Text("\(profileViewModel.user.gender)")
.font(.custom("Poppins-Regular", size: 22))
.fontWeight(.bold)
.foregroundColor(Color.black)
Text(profileViewModel.user.role.first ?? "")
.font(.custom("Poppins-Regular", size: 22))
.fontWeight(.bold)
.foregroundColor(Color.black)
Button(action: {
// save the profileViewModel.user to database
profileViewModel.saveUser() // <--- here
}) {
Text("Save")
}
}
.padding()
}
}
struct CategoriesView: View {
#ObservedObject var viewModel = SectionViewModel()
#EnvironmentObject var loginViewModel : LoginViewModel
#StateObject var profileViewModel = ProfileViewModel()
var body: some View {
ZStack {
VStack (alignment: .leading, spacing:0) {
EditProfile(profileViewModel: profileViewModel) // <--- here
.padding()
.padding(.bottom,-10)
}
}
.onAppear() {
self.viewModel.fetchData()
profileViewModel.fetchData(userId: loginViewModel.session?.uid)
}
}
}
EDIT1: regarding the updated code.
In your new code, in ProfileHost you are not passing ProfileViewModel.
Use:
NavigationLink(destination: ProfileEditor(profileViewModel: viewModel)) {
ProfileRow(profileSetting: profile)
}
And in ProfileEditor replace profile with profileViewModel.user
You will probably need to adjust profileItem and put it in a .onAppear {...} . Something like this:
struct ProfileEditor: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#ObservedObject var profileViewModel: ProfileViewModel
#EnvironmentObject var loginViewModel: LoginViewModel
let profileLabel: [String] = ["Name", "Account", "Gender", "Role", "Email"]
#State var profileItem: [String] = []
#State var profileEditorRow: [ProfileEditorItem] = []
var body: some View {
VStack {
ForEach(profileEditorRow) { editor in
if editor.id == 5 {
ProfileEditorRow(editor: editor, showLastLine: true)
} else {
ProfileEditorRow(editor: editor, showLastLine: false)
}
}
Button("Save") {
profileViewModel.updateData(userId: loginViewModel.session?.uid)
}
}
.onAppear {
profileItem = [profileViewModel.user.name,
profileViewModel.user.birthDay,
profileViewModel.user.gender,
profileViewModel.user.role.first ?? "",
profileViewModel.user.birthDay]
for n in 1...5 {
profileEditorRow.append(ProfileEditorItem(id: n, label: profileLabel[n-1], item: profileItem[n-1]))
}
}
}
}
EDIT2: update func
func updateData() {
ref.("users").child(user.id).updateChildValues([
"name": user.name,
"birthDay": user.birthDay,
"gender": user.gender,
"role": user.role.first ?? ""])
}
and use this in ProfileEditor :
Button("Save") {
profileViewModel.updateData()
}

How can i remove the trailing red animation at the end of swipe deleting using .onDelete swiftUI

This is the code :
struct ContentView: View {
#State var names = ["A" , "B", "C", "D"]
var body: some View {
List {
ForEach(names, id: \.self ) { name in
Group {
testStruct(name: name)
}
}.onDelete(perform: removeItems)
}
}
private func removeItems (indexSet: IndexSet) {
names.remove(atOffsets: indexSet)
}
}
struct testStruct: View , Identifiable {
#State var name: String
let id = UUID()
var body: some View {
HStack {
Text(name)
Spacer()
Image(systemName: "folder.fill")
}
}
}
I am unable to remove the trailing red animation on swiping onDelete . Is there any elegant way of doing that . .animation() seem not to be working

SwiftUI Drag And Drop with List

I try to implement drag and drop between two views as shown in the code bellow, when I embed the "ForEach" view inside Group its work well, but when I try to embed it in the "List" view it doesn't work and I don't know why, any one can help to solve this problem ??
import SwiftUI
import MobileCoreServices
struct DragAndDropExample: View {
var delegate = dropDelegate()
#State var data = [SomeData(id: 1), SomeData(id: 2), SomeData(id: 3), SomeData(id: 4), SomeData(id: 5)]
#State var selectedDate: [SomeData] = []
var body: some View {
VStack (spacing: 16) {
Group { // Try to maek this "List"
ForEach(data) { dat in
Image(systemName: "\(dat.data).square.fill")
.resizable()
.background(Color.blue)
.frame(width: 50, height: 50)
.cornerRadius(15)
.onDrag{
NSItemProvider(item: .some(URL(string: dat.data)! as NSSecureCoding), typeIdentifier: String(kUTTypeURL))
}
}
}
Group { // Try to maek this "List"
if selectedDate.isEmpty {
Text("Drop Here")
}else {
ForEach(selectedDate) { dat in
Text("\(dat.data)")
}
}
}
.background(Color.blue)
.onDrop(of: [String(kUTTypeURL)], delegate: delegate)
}
}
}
struct DragAndDropExample_Previews: PreviewProvider {
static var previews: some View {
DragAndDropExample()
}
}
class SomeData: Identifiable {
let id: Int
let data: String
init(id: Int) {
self.id = id
self.data = String(id)
}
}
class dropDelegate: DropDelegate {
func performDrop(info: DropInfo) -> Bool {
// Drag and drop code here...
return true
}
}