Unwrap Optional Value from Dictionary - ios

I'm having an issue unwrapping a value thats stored in an Ordered Dictionary. I created a reproducible example.
I'm pulling data from a database and the format looks like ["isCompleted": Optional(false), "_id": Optional("62cef664008fd87e00e60667"), "body": Optional("run")]
I then create a Document object and store that in a list of those objects (in this sample there is only one).
I'm trying to display the value of the Ordered Dictionary in the Document Object, but can't seem to unwrap the Optional value no matter what I try.
import SwiftUI
import OrderedCollections
class ContentViewModel : ObservableObject {
var orderedDict = OrderedDictionary<String, Any?>()
#Published var docsList: [Document] = []
init() {
let doc: [String: Any?] = ["isCompleted": false, "_id": "62cef664008fd87e00e60667", "body": "run"]
for (key, value) in doc {
self.orderedDict[key] = value
}
self.docsList.append(Document(id: UUID().description, value: self.orderedDict))
}
}
struct ContentView: View {
#StateObject var viewModel = ContentViewModel()
var body: some View {
ForEach(viewModel.docsList, id: \.self) { doc in
if let val = doc.value["isCompleted"] {
Text(val.debugDescription)
}
if(doc.value["isCompleted"] != nil) {
Text(doc.value["isCompleted"]?.debugDescription ?? "")
}
Text(doc.value["isCompleted"]?.debugDescription ?? "")
}
}
}
import Foundation
import OrderedCollections
class Document : Hashable, Equatable{
static func == (lhs: Document, rhs: Document) -> Bool {
lhs.id.description == rhs.id.description
}
let id: String
let value: OrderedDictionary<String,Any?>
init(id: String, value: OrderedDictionary<String,Any?>) {
self.id = id
self.value = value
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}

Related

Cannot convert value of type 'Published<>.Publisher' to expected argument type 'Binding<>'

I'm having a bit of a complicated construction and have trouble figuring out how to get it working:
class Parent : Codable, ObservableObject {
#Published public var children: [Child]?
public func getChildren(with name: String) -> [Child] {
return children?.filter { $0.name == name } ?? []
}
}
class Child : Codable, Hashable, ObservableObject {
static func == (lhs: Child, rhs: Child) -> Bool {
return lhs.name == rhs.name && lhs.isSomething == rhs.isSomething
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(isSomething)
}
let name: String
#Published var isSomething: Bool
}
...
struct MyView : View {
#ObservedObject var parent: Parent
var names: [String]
var body: some View {
ForEach(names, id: \.self) { name in
...
ForEach(parent.getChildren(with: name), id: \.self) { child in
Toggle(isOn: child.$isSomething) { <== ERROR HERE
...
}
}
}
}
}
I had also tried Toggle(isOn: $child.isSomething) which of course leads to Cannot find '$child' in scope.
How do I resolve this? In more detail: How do I return the correct type from getChildren() that allows $child.isSomething for example?
(BTW, I used this to allow an ObservableObject class to be Codable. Although this seems unrelated, I've let this into my code extraction above because perhaps it matters.)
We can use separate #Published property with .onChange .
struct ContentView: View {
#ObservedObject var parent: Parent
#State var names: [String] = ["test1", "test2"]
var body: some View {
ForEach(names, id: \.self) { name in
GroupBox { // just added for the clarity
ForEach($parent.filteredChildren) { $child in
Toggle(isOn: $child.isSomething) {
Text(child.name)
}
}
}
}
.onChange(of: names) { newValue in
parent.updateChildren(with: "test") //<- here
}
.onAppear{
names.append("test3")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(parent: Parent(children:[ Child(name: "test"), Child(name: "test2")]))
}
}
class Parent: ObservableObject {
init(children: [Child]) {
self.children = children
}
#Published public var children: [Child]
#Published public var filteredChildren: [Child] = []
func updateChildren(with name: String) {
filteredChildren = children.filter { $0.name == name }
}
}
class Child: ObservableObject, Identifiable {
init(name: String) {
self.name = name
}
let id = UUID().uuidString
let name: String
#Published var isSomething: Bool = false
}

SwiftUI List with sections using dictionary of array

I have an array of users returned. I want to group them by created date and then display them in SwiftUI List with sections. The section title is the date. After I group the users I end up with a Dictionary of user arrays and the key is the date that group all users that have been created in the same date I want to use the key as a section and the value (user array) as List rows. It seems that List only works with array. Any clean solution to prepare the data for the List so it can display the sections and its content ?
import SwiftUI
import Combine
struct ContentView: View {
#EnvironmentObject var interactor: Interactor
var body: some View {
List(interactor.users) { user in // Error: Cannot convert value of type '[String : [User]]' to expected argument type 'Range<Int>'
}
.onAppear {
interactor.loadUsers()
}
}
}
class Interactor: ObservableObject {
#Published private(set) var users = [String: [User]]()
func loadUsers() {
let allUsers = [User(id: "1", name: "Joe", createdAt: Date()),
User(id: "2", name: "Jak", createdAt: Date())]
users = Dictionary(grouping: allUsers) { user in
let dateFormatted = DateFormatter()
dateFormatted.dateStyle = .short
return dateFormatted.string(from: user.createdAt)
}
}
}
struct User: Identifiable {
let id: String
let name: String
let createdAt: Date
}
You can use sections inside List, and make use of its header parameter to pass Dates. I have already fetched all dates as [String] by using separate function. One thing you need to figure is the way you are going to pass multiple keys to fetch data for different date keys that you will have. May be you can create that first by using [Users] objects and save all keys.
Below is the solution to your issue-:
ContentView-:
//// Created by TUSHAR SHARMA on 06/02/21.
////
//
import SwiftUI
struct ContentView: View {
#EnvironmentObject var interactor: Interactor
var body: some View {
List {
ForEach(getAllDates(),id:\.self) { dates in
Section(header: Text(dates)) {
ForEach(interactor.users[dates] ?? []) { users in
Text(users.name)
}
}
}
}
}
func getAllDates() -> [String]{
let getObjects = interactor.users[Date().stringFromDate()] ?? []
var dateArray : [String] = []
for getData in getObjects{
dateArray.append(getData.createdAt.stringFromDate())
}
let unique = Array(Set(dateArray))
return unique
}
}
extension Date{
func stringFromDate() -> String{
let dateFormatted = DateFormatter()
dateFormatted.dateStyle = .short
return dateFormatted.string(from: self)
}
}
class Interactor: ObservableObject {
#Published private(set) var users = [String: [User]]()
init() {
loadUsers()
}
func loadUsers() {
let allUsers = [User(id: "1", name: "Joe", createdAt: Date()),
User(id: "2", name: "Jak", createdAt: Date())]
users = Dictionary(grouping: allUsers) { user in
user.createdAt.stringFromDate()
}
}
}
struct User: Identifiable {
let id: String
let name: String
let createdAt: Date
}
#main view
// Created by TUSHAR SHARMA on 07/01/21.
//
import SwiftUI
#main
struct WaveViewApp: App {
let interactor = Interactor()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(interactor)
}
}
}

Problems saving data to UserDefaults

I'm struggling with saving some date to UserDefaults. I have a struct, an array of which I'm going to save:
struct Habit: Identifiable, Codable {
var id = UUID()
var name: String
var comments: String
}
Then, in the view, I have a button to save new habit to an array of habits and put it into UserDefaults:
struct AddView: View {
#State private var newHabit = Habit(name: "", comments: "")
#State private var name: String = ""
let userData = defaults.object(forKey: "userData") as? [Habit] ?? [Habit]()
#State private var allHabits = [Habit]()
var body: some View {
NavigationView {
Form {
Section(header: Text("Habit name")) {
TextField("Jogging", text: $newHabit.name)
}
Section(header: Text("Description")) {
TextField("Brief comments", text: $newHabit.comments)
}
}
.navigationBarTitle("New habit")
.navigationBarItems(trailing: Button(action: {
allHabits = userData
allHabits.append(newHabit)
defaults.set(allHabits, forKey: "userData")
}) {
addButton
})
}
}
}
When I tap the button, my app crashes with this thread: Thread 1: "Attempt to insert non-property list object (\n \"HabitRabbit.Habit(id: 574CA523-866E-47C3-B56B-D0F85EBD9CB1, name: \\\"Wfs\\\", comments: \\\"Sdfdfsd\\\")\"\n) for key userData"
What did I do wrong?
Adopting Codable doesn't make the object property list compliant per se, you have to encode and decode the object to and from Data.
Something like this
func loadData() -> [Habit]
guard let userData = defaults.data(forKey: "userData") else { return [] }
return try? JSONDecoder().decode([Habit].self, from: userData) ?? []
}
func saveData(habits : [Habit]) {
guard let data = try? JSONEncoder().encode(habits) else { return }
defaults.set(data, forKey: "userData")
}

Cannot resolve "Type of expression is ambiguous without more context" error. Can someone check my code?

I'm relatively new to SwiftUI and time to time getting errors and solving them by searching over the internet but this time I could not find any solution to my problem and decided to ask for some help over here, stack overflow. I hope the code below helps you to find my issue.
Both my struct are Identifiable and I actually used ShoppingList struct in the same view to make a List of it with the same technique and it works without an error. But when I try to use ForEach for a variable of ShoppingList struct (which is also a struct and conforms to Identifiable protocol) I get this error "Type of expression is ambiguous without more context"
This is the view that I get my error:
struct ListDetailView: View {
#EnvironmentObject var session: SessionStore
var item: ShoppingList
#State private var isAddNewViewActive: Bool = false
var body: some View {
List {
Section(header: Text("Products")) {
ForEach(self.item.products, id: \.id) { product in <<<--- ERROR LINE
Text(product.name)
}
}
Section(header: Text("")) {
Button(action: { self.isAddNewViewActive.toggle() } ) {
Text("Click to add new product")
}
}
}
.listStyle(GroupedListStyle())
.navigationBarTitle(self.item.name)
.sheet(isPresented: $isAddNewViewActive) {
AddNewItemView(session: self.session, item: self.item, isViewActive: self.$isAddNewViewActive)
}
}
}
These are the structs that are in the code
struct ShoppingList: Identifiable, Equatable {
var id: UUID
var name: String
var coverPhoto: String
var products: [Product]
init(id: UUID = UUID(), name: String, coverPhoto: String = "cart", products: [Product] = [Product]()) {
self.id = id
self.name = name
self.coverPhoto = coverPhoto
self.products = products
}
mutating func addProduct(product: Product) {
products.append(product)
print(products)
}
}
struct Product: Identifiable, Equatable {
var id: UUID
var name: String
var brand: String
var imageURL: String
var links: [Int: String]
var description: String
init(id: UUID = UUID(), name: String, brand: String = "", imageURL: String = "", links: [Int: String] = [:], description: String = "") {
self.id = id
self.name = name
self.brand = brand
self.imageURL = imageURL
self.description = description
self.links = links
}
}
Thanks in advance to all StackOverflow Community.
i properly conform to the Equatable protocol
struct ShoppingList: Identifiable, Equatable {
static func == (lhs: ShoppingList, rhs: ShoppingList) -> Bool {
return lhs.id == rhs.id && rhs.id == lhs.id
}
var id: UUID()
...
init(name: String, brand: String = "", imageURL: String = "", links: [Int: String] = [:], description: String = "") {
...
}
}
no need to init UUID, UUID() will self generate
Apparently, there was an error in a completely unrelated part of the code snippet I posted here (sheet View that pops when I click the button on View that has error) and that was causing the error :/
The code I posted here works just fine.

pass values dynamically for network request

I have to pass the value of movie.id which is received from a View which is called ReviewView.
I need to pass the movie.id value received in this view to ReviewFetcher and then make a network request using that movie.id. As of now I have hard coded the movie id in ReviewFetcher but I require this to be received from ReviewView and then make a request and then update the list in ReviewView.
Below is the Code:-
ReviewFetcher.swift
import Foundation
import Alamofire
import SwiftUI
class ReviewObserver: ObservableObject {
#Published var review = ReviewArray(id: 1, page: 9, results: [])
// #State var movieID:Int
init() {
// self.movieID = movieID
getReviews(movieID : 181812)
}
func getReviews(movieID:Int) {
//self.review.results.removeAll()
let reviewURL = "https://api.themoviedb.org/3/movie/"+String(movieID)+"/reviews?api_key=a18f578d774935ef9f0453d7d5fa11ae&language=en-US&page=1"
Alamofire.request(reviewURL)
.responseJSON { response in
if let json = response.result.value {
if (json as? [String : AnyObject]) != nil {
if let dictionaryArray = json as? Dictionary<String, AnyObject?> {
let json = dictionaryArray
if let id = json["id"] as? Int,
let page = json["page"] as? Int,
let results = json["results"] as? Array<Dictionary<String, AnyObject?>> {
for i in 0..<results.count {
if let author = results[i]["author"] as? String,
let content = results[i]["content"] as? String,
let url = results[i]["url"] as? String {
let newReview = ReviewModel(author: author,
content: content,
url: url)
self.review.results.append(newReview)
}
}
}
}
}
}
}
}
}
ReviewView.swift
import SwiftUI
struct ReviewsView: View {
#State var movie: MovieModel
#Binding var reviews:[ReviewModel]
#ObservedObject var fetcher = ReviewObserver()
var body: some View {
VStack(alignment:.leading) {
Text("Review")
.font(.largeTitle)
.bold()
.foregroundColor(Color.steam_rust)
.padding(.leading)
Divider()
// Text(String(fetcher.movieID))
List(fetcher.review.results) { item in
VStack(alignment:.leading) {
Text("Written by : "+item.author)
.font(.body)
.bold()
.padding(.bottom)
Text(item.content)
.font(.body)
.lineLimit(.max)
}
}
}
}
}
MovieModel.swift
import Foundation
import SwiftUI
import Combine
struct MovieArray: Codable {
var page: Int = 0
var total_results: Int = 0
var total_pages: Int = 0
var results: [MovieModel] = []
}
struct MovieModel: Codable, Identifiable {
var id : Int
var original_title: String
var title: String
var original_language:String
var overview: String
var poster_path: String?
var backdrop_path: String?
var popularity: Double
var vote_average: Double
var vote_count: Int
var video: Bool
var adult: Bool
var release_date: String?
}
Remove the init() of your ReviewObserver class. and then call getReviews method in .onAppear modifier of your VStack. The idea of what you need:
class ReviewObserver: ObservableObject {
#Published var review = ReviewArray(id: 1, page: 9, results: [])
func getReviews(movieID:Int) {
//you block,, anything you wanna do with movieID.
//Assume you are going to change 'review' variable
}
}
struct ReviewsView: View {
#State var movie:MovieModel
#Binding var reviews:[ReviewModel]
#ObservedObject var fetcher = ReviewObserver()
var body: some View {
VStack(alignment:.leading){
Text("Review")
Divider()
Text(String(fetcher.movieID))
List(fetcher.review.results)
{
item in
VStack(alignment:.leading){
Text("Written by : "+item.author)
}
}.onAppear {
self.fetcher.getReviews(movieID: movie.id)
}
}
}

Resources