【SwiftUI】How to initialize an empty struct - arrays

I need some kind help in SwiftUI.
I have a model below that takes in JSON data.
When I want to initialize it as an empty array, it should be simple, just stating Answers, but how can I initialize it not as an array but an empty single data set.
struct Answers: Identifiable, Decodable {
let id: Int
let questionsUsersId: Int
let reactions: [Reactions]
enum CodingKeys: String, CodingKey {
case id = "questions_id"
case questionsUsersId = "questions_users_id"
case reactions = "reactions"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
self.questionsUsersId = try container.decodeIfPresent(Int.self, forKey: .questionsUsersId) ?? 0
self.reactions = try container.decodeIfPresent([Reactions].self, forKey: .reactions) ?? [Reactions]()
}
}
I've tried var answer: Answers = Answers( ) but got the errors below:
//Missing argument for parameter 'from' in call
//Insert 'from: <#Decoder#>'
The reason I want to initialize it empty is because I need to hand over the default value of it when the data model is nil. (i.e. what should I write in the xxx?)
#ObservedObject var receiveAnswersViewModel = ReceiveAnswersViewModel()
//abbreviate
NavigationLink(destination: AnswerDetailView(answer: receiveAnswersViewModel.answer ?? xxx), isActive: $shouldNavigate) {
ForEach(myPageViewModel.answers(forFilter: selectedFilter)?.answers ?? [Answers]()) { answer in
AnswerCell(answer: answer)
.gesture(TapGesture()
.onEnded({ _ in
//your action here
self.answersId = answer.answersId
receiveAnswersViewModel.fetchAnswers(answers_id: answersId ?? 0)
}))
}
}
This is what I'm trying to do above: Show several cells using NavigationLink. (Imagine that the cells are tweets.) Press one of the tweets in the list → Call fetchAnswers func in the View Model which takes the pressed tweet id as the argument → Returns the specific tweet detail info→ Put it in the Answer model → Hand it over to the View → Navigate to the Tweet Detail View.
View Model code is below:
class ReceiveAnswersViewModel: ObservableObject {
#Published var answer: Answers?
func fetchAnswers(answers_id: Int){
//abbreviate
self.answer = try JSONDecoder().decode(Answers.self, from: data)
}
Thanks in advance.

A reasonable way is to add a static method to create an empty instance. In this case you have also to add the init method
By the way name the struct in singular form, an instance represents likely one Answer
struct Answer: Identifiable, Decodable {
let id: Int
let questionsUsersId: Int
let reactions: [Reactions]
static let example = Answer(id: 0, questionsUsersId: 0, reactions: [])
init(id: Int, questionsUsersId: Int, reactions: [Reactions]) {
self.id = id
self.questionsUsersId = questionsUsersId
self.reactions = reactions
}
...
Then you can declare
var answer = Answer.example

Related

Weekly activity summary help? SWIFTUI

I'm tryin' to obtain a list of activities ("dd/mm/YY: goal achieved/missed goal") which has to be setted every week. The problem is that I obtain a list of activities with the same date and the same result of the previous one. For example:
28/02/2022: goal achieved
28/02/2022: goal achieved
28/02/2022: goal achieved
and the next day:
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
I want to obtain, instead, a list like:
28/02/2022: goal achieved
01/03/2022: missed goal
02/03/2022: goal achieved...
These are useful structs:
struct Persistent {
#AppStorage("goalAchieved") static var goalAchieved : Bool = false
#AppStorage("activityList") static var activityList : [String] = []
}
struct obj {
static var currentDate = Date()
static var stringDate = ""
static var activity = Activity(date:Persistent.lastUpdatedDate)
}
This is the ActivityListView:
import SwiftUI
func activitystring(activity:Activity) -> String{
var output = ""
output = "\(activity.date): \(activity.reachedobj(goalAchieved: Persistent.goalAchieved))"
return output
}
struct Activity: Identifiable{
let id = UUID()
let date: String
func reachedobj(goalAchieved: Bool) -> String {
var output = ""
if Persistent.goalAchieved == false { output = "Missed goal" }
if Persistent.goalAchieved == true { output = "Goal Achieved!"}
return output
}
}
struct ActivityRow: View{
var activity: Activity
var body: some View{
Text(activitystring(activity: activity))
Divider()
}
}
struct ActivityListView: View {
var body: some View {
ScrollView{
Text("Week summary").font(.system(size: 15)).foregroundColor(Color.green)
Text("")
ForEach(Persistent.activityList, id: \.self) { activity in
let activity = Activity(date: Persistent.lastUpdatedDate)
ActivityRow(activity: activity)
}
}
}
}
Finally this is the useful code in the ApplicationApp file (main) where I update activity list:
MenuView().onAppear(){
if Persistent.activityList.count>7{
Persistent.activityList.removeAll()
}
obj.currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
obj.stringDate = formatter.string(from:obj.currentDate)
if Persistent.lastUpdatedDate != obj.stringDate{
Persistent.goalAchieved = false
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList.append(activitystring(activity: activity))
Persistent.lastUpdatedDate = obj.stringDate
}
}
What's wrong on this?
You're calling obj.activity in your ForEach and ActivityRow, that's why it repeats that same static property all over the place.
You better just drop your struct obj and try again without it
In your Persistent object you have an array of many activities, called activitylist , but one single boolean that tells if the goal is achieved - goalachieved indeed.
Your view is iterating through the array of Persistent.activitylist, so you will have many lines for one single result - achieved or not achieved. You might actually want to iterate over an array of Persistent objects - meaning that somewhere you should probably store [Persistent] in some variable. In this way, you will see one line only for each result.
If I also may suggest: use the conventions for naming variables, Swift uses "camelCaseConventionForVariables", easier to read than "thewholevariableislowercase"
Edit:
Let me try to change a little bit your code (I would personally change it more radically, but that's not the scope of the answer).
Instead of having only one goalAchieved for all elements on the array activityList, make it a dictionary:
struct Persistent {
// Drop this variable
// #AppStorage("goalAchieved") static var goalAchieved : Bool = false
// Make this a dictionary, the date will be the key and the goalAchieved will be the value
#AppStorage("activityList") static var activityList : [String: Bool] = [:]
}
Add values to the dictionary (#meomeomeo is right, you don't need obj):
MenuView().onAppear() {
if Persistent.activityList.count > 7 {
Persistent.activityList.removeAll()
}
let currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
let stringDate = formatter.string(from: currentDate)
if Persistent.lastUpdatedDate != stringDate {
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList[activitystring(activity: activity))] = false // Will create something like ["01/03/2022": false]
Persistent.lastUpdatedDate = stringDate
}
}
Iterate on the dictionary in your ForEach; for more info: read here.

In Swiftui, I have an array that is #Published. I use .filter on the array but get error that member elements does not exist

The object definition is:
struct Course: Identifiable, Hashable, Codable {
#DocumentID var id: String?
var country: String
var createdBy: String
}
I then embed this object as a member of a class as so:
class CourseRepository: ObservableObject {
private let path: String = "Courses"
private let store = Firestore.firestore()
#Published var courses: [Course] = []
...
In the snippet below, I only want the map result to contain courses where 'createdBy' == to the uid of the currently logged in user. But on the .filter statement, I get the following error:
emphasized text
**Value of type 'Published<[Course]>.Publisher.Output' (aka 'Array<Course>') has no member 'createdBy'**
MyCourseRepository.$courses
.filter { $0.createdBy == self.authState.loggedInUser!.uid }
.map { courses in courses.map(CourseViewModel.init)}
.assign(to: \.MyCourseViewModels, on: self)
.store(in: &cancellables)
}
Any help on this one much appreciated!
Right now, you're trying to filter the Publisher, whereas what you want to do is filter the items in the array that it's sending:
MyCourseRepository.$courses
.map { courseArray in
courseArray.filter { course in
course.createdBy == self.authState.loggedInUser!.uid
}
}

Weird results with parsing array from json in Swift3

Hi guys I don't know why the array Places returns weird values like 0x6080004b3aa0 instead of displaying my title, coordinate and subtitle out of my JSON url. Thanks for your Help!
import MapKit
#objc class Place: NSObject {
var title: String?
var coordinate: CLLocationCoordinate2D
var subtitle: String?
init(title:String,subtitle:String, coordinate:CLLocationCoordinate2D){
self.title = title
self.coordinate = coordinate
self.subtitle = subtitle
}
static func getPlaces() -> [Place] {
guard let url = NSURL(string: "https://script.googleusercontent.com/macros/echo?user_content_key=Z-LfTMdhgAg_6SRd-iMucSyWu-LFBQO8MLxJZ6DPcL05Rtr3joCCypWD2l46qaegSpVpVINc1DLl5inoDOgGx3p3ANpY1AkGOJmA1Yb3SEsKFZqtv3DaNYcMrmhZHmUMWojr9NvTBuBLhyHCd5hHa1ZsYSbt7G4nMhEEDL32U4DxjO7V7yvmJPXJTBuCiTGh3rUPjpYM_V0PJJG7TIaKp4bydEiKBUZP6fpOyGJIhkmEGneM7ZIlWloTVbXmkjs15vHn8T7HCelqi-5f3gf3-sKiW3k6MDkf31SIMZH6H4k&lib=MbpKbbfePtAVndrs259dhPT7ROjQYJ8yx") else { return [] }
let request = NSMutableURLRequest(url: url as URL!)
var places = [Place]()
let task = URLSession.shared.dataTask(with: request as URLRequest) {data,response,error in
guard error == nil && data != nil else {
print ("Error:",error)
return
}
let httpStatus = response as? HTTPURLResponse
if httpStatus?.statusCode == 200
{ if data?.count != 0
{
let responseString = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! NSDictionary
let contacts = responseString["Sheet1"] as? [AnyObject]
for contact in contacts!{
var places = [Place]()
let title = contact["name"] as! String
let subtitle = contact["description"] as? String
let latitude = contact["latitude"] as? Double ?? 0, longitude = contact["longitude"] as? Double ?? 0
let place = Place(title:title,subtitle:subtitle!,coordinate: CLLocationCoordinate2DMake(latitude, longitude))
places.append(place)
print(latitude)
print(place)
}
}
else {
print("No data got from url")
}
} else {
print("error httpsatus code is:", httpStatus!.statusCode)
}
}
task.resume()
return places as [Place]
}
}
I think the problem is this:
let place = Place(title:title,subtitle:subtitle!,coordinate: CLLocationCoordinate2DMake(latitude, longitude))
When I print(place) it returns the weird results
When you make a class that subclasses from NSObject you're creating a object that is backed by an Objective-c class -- which in some circumstances can be really useful (most common use is when you want to take your object and archive it as a blob of binary data).
I'm guessing that in your case, you probably don't want/need to subclass NSObject.
Here's a simplified example to show what's happening:
Here's a class backed by NSObject:
#objc class ObjCPlace: NSObject {
let name: String
init(name: String) {
self.name = name
}
}
If you create an instance of this object and try to print contents - like you've found, you get the objects location in memory:
let testObjcPlace = ObjCPlace(name: "New York")
print(testObjcPlace)
// prints:
// <__lldb_expr_310.ObjCPlace: 0x600000055060>
On alternative to using print could be to use dump that provides a more detailed look at your object:
let testObjcPlace = ObjCPlace(name: "New York")
dump(testObjcPlace)
// Prints:
// ▿ <__lldb_expr_310.ObjCPlace: 0x600000055060> #0
// - super: NSObject
// - name: "New York"
But instead of making an NSObject subclass, you probably just want to make a Swift class (or in this example a struct take a look at this question and answers for explanations of the differences)
struct Place {
let name: String
init(name: String) {
self.name = name
}
}
Because this object isn't an Objective-c object you can use print and get the internal properties printed as well:
let testPlace = Place(name: "New York")
print(testPlace)
// Prints:
// Place(name: "New York")
p/s welcome to StackOverflow!

Filtering arrays for use with UISearchBar

I have a table view which displays a user's Name, Company Name and Photo (PFFile). Each tableView row I have has all of this information in it.
I am using UISearchBarDelegate and IB to implement a search function to filter by the user's Name. It is finding the correct user but I have not been able to also update the company photo.
How do I filter the other arrays? The items I need from the arrays will be at the same index as the ones taken from the user's Name array.
EDIT: I am trying a different data structure and am receiving array index out of range, updated code below:
var filterArray = [User]() //<-- globally declared
var userArray = [User]() //< Global
class User {
var name: String?
var company: String?
init (name: String?, company: String?) {
self.name = name
self.company = company
}
}
//In a class which populates the search arrays
for object in unwrappedSucceeded {
let username = object.valueForKey("username") as! String
let companyName = object.valueForKey("companyName") as! String
let user = User(name: username, company: companyName)
userArray.append(user)
}
//tableViewController
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
filterArray.removeAll(keepCapacity: false)
if searchText.characters.count != 0 {
isSearch = true
self.search(searchText)
} else {
isSearch = false
}
}
func search(text: String) -> Void {
filterArray = userArray.filter({$0.name == text})
}
//In cellForRowAtIndexPath
cell.usernameCell.text = filterArray[indexPath.row].name //ARRAY INDEX OUT OF RANGE
Like I said you strongly recommend to group each user's info into one big container, therefore we could use array of struct or class, then it comes easier to filter.
schematic for the container:
struct Container
{
var username:String?
var companyName:String?
var photo:UIImage?
}
your main array will be : var arrayofData = [Container]()
Now when you are query your objects from parse, inside of your query function
// after you called the findObjectsWithBackgroundBlock()
// let's assume you check for error and if the [PFObject] is empty or not
for one in objectsFromParse
{
let photoToget = one["Photo"] as! PFFile
// next step should be to get the image data right :)
{
// let's assume that is the block when get the image data right:)
// check your data and assign it to some UIImage
// then
let userRepresentation = Container() //<-- we are creating a single object representation for each user
let username = one["username"] as! String //<--data we got from Parse
let companyName = one["companyName"] as! String
let userImage = //the UIImage which contains the data
userRepresentation.username = username
userRepresentation.companyName = companyName
userRepresentation.photo = userImage
// then we append
arrayOfData.append(userRepresentation)
}
}
Now we have all data into our array, so let's filter by username and also I hope you configure your tableView so when you have data from filter or regular array.
var filterArray = [Container]() //<-- globally declared
func search(text: String) -> Void
{
filterArray = arrayOfData.filter(){ (Container) -> Bool in
let range = Container.name!.rangeOfString(text, options:NSStringCompareOptions.CaseInsensitiveSearch) return range != nil }
// then you are good to go
}
let arr1 = [10,20,40]
let e1 = arr1.enumerate()
let arr2 = ["a","b","c"]
let f1 = e1.filter { $0.element % 20 == 0 }
let f2 = arr2.enumerate().filter { j, _ in
f1.contains { i, _ in
i == j
}
}
print(f1.map{$0.element}, f2.map{$0.element})
// [20, 40] ["b", "c"]
now you have both arrays "filtered". the best, what you can do is redesigning your data model!

Swift 1.2 Filter an Array of Structs by keyword

I need some help filtering an array of Structs.
This is what I am doing currently, it filters the array but not correctly.
For example lets say I search for an item in the array with "Mid" I have one item that should be shown however the item shown starts with "Bad".
var array = breweries.filter() { $0.name?.lowercaseString.rangeOfString(searchController.searchBar.text.lowercaseString) != nil }
results = array
here is my Struct
struct Breweries {
let name: String?
let breweryId: String?
let distance: Double?
let largeIconURL: String?
let streetAddress: String?
let locality: String?
let region: String?
let phone: String?
let website: String?
init(brewDictionary: [String: AnyObject]) {
name = brewDictionary["brewery"]?["name"] as? String
breweryId = brewDictionary["breweryId"] as? String
distance = brewDictionary["distance"] as? Double
largeIconURL = brewDictionary["brewery"]?["images"]??.objectForKey("large") as? String
streetAddress = brewDictionary["streetAddress"] as? String
locality = brewDictionary["locality"] as? String
region = brewDictionary["region"] as? String
phone = brewDictionary["phone"] as? String
website = brewDictionary["website"] as? String
}
}
Please point in the right direction!
Note: I am using Swift 1.2
Update:
I thought a video would be of help to better explain what I am trying to do.
Demo Of issue
What I want is to find the filter the array so only the item with a similar name is shown.
Update 2: As it turns out I forgot to handle the case when my UISearchController was active.
Assuming your Struct name is Breweries and it has a name property, try this:
let array = breweries.filter() {
($0.name!.lowercaseString as NSString).containsString(searchController.searchBar.text.lowercaseString)
}
Your usage of filter is correct, but your closure seem to be complicated with no clear goal. I suggest you to write an extension (or possibly use what I am using):
extension String {
func contains(search: String, ignoreCase: Bool = false, ignoreDiacritic: Bool = false) -> Bool {
var options = NSStringCompareOptions.allZeros
if ignoreCase { options |= NSStringCompareOptions.CaseInsensitiveSearch }
if ignoreDiacritic { options |= NSStringCompareOptions.DiacriticInsensitiveSearch }
return self.rangeOfString(search, options: options) != nil
}
}
This way you can use closure like this to search:
breweries.filter() {
$0.name?.contains("x") // Precise search
$0.name?.contains("x", ignoreCase: true, ignoreDiacritics: true) // Ignores diacritics and lower / upper case
}
of course, you can use | or & to search for multiple parameters
breweries.filter() {
$0.name?.contains("x") || $0.streetAddress?.contains("x")
}
Hope it helps!
Here is an example from an investing app with struct:
import Foundation
public struct SNStock {
public let ticker:NSString
public let name:NSString
init(ticker:NSString, name:NSString) {
self.ticker = ticker
self.name = name
}
}
Search on Main Thread:
public func searchStocksByKeyword(keyword:String) -> [SNStock] {
let lowercaseKeyword = keyword.lowercaseString
var searchResults:[SNStock] = []
searchResults = stocks.filter({ (stock:SNStock) -> Bool in
return stock.ticker.lowercaseString.hasPrefix(lowercaseKeyword)
})
if (searchResults.count == 0) {
searchResults = stocks.filter({ (stock:SNStock) -> Bool in
return stock.name.lowercaseString.hasPrefix(lowercaseKeyword)
})
}
searchResults.sortInPlace {
($0.ticker as String) < ($1.ticker as String)
}
return searchResults;
}
Search on Background Thread:
public func searchStocksByKeyword(keyword:String, completion:(stocks:[SNStock])->()) {
let qualityOfServiceClass = QOS_CLASS_USER_INTERACTIVE
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
let stocks:[SNStock] = self.searchStocksByKeyword(keyword)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(stocks: stocks)
})
})
}

Resources