Query relation images from Parse that uses Back4App database - ios

So I have an ParseObject setup as this - This is the main Object in Parse called MGLocation:
struct MGLocation: ParseObject {
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var originalData: Data?
var ACL: ParseACL?
var title: String?
var category: String?
init() {}
init(objectId: String?) {
self.objectId = objectId
}
}
Then I have my Codable setup using the following code:
struct Place: Codable, Identifiable {
let id: Int
var b4aId = ""
let title: String?
let category: String
init(
id: Int,
title: String?,
category: String,
) {
self.id = id
self.title = title
self.category = category
}
init(with p: MGLocation) {
self.id = atomicId.wrappingIncrementThenLoad(ordering: .relaxed)
self.b4aId = p.objectId ?? ""
self.title = p.title ?? "Undefined"
self.category = p.category ?? "Uncategorized"
}
}
Then I have the following function which pulls in the MGLocation:
func fetchPlaces() {
let query = MGLocation.query().limit(1000)
query.find { [weak self] result in
guard let self = self else {
return
}
switch result {
case .success(let items):
self.places = items.map({
Place(with: $0)
})
case .failure(let error):
}
}
}
Questions:
What is the best way that I can pull in a relational column? Inside MGLocation, I have a related column called images which can access another object.
This calls MGImage which has the following columns:
id, title, column, mime
Does anyone know how I can infuse and pull in the related column also? All help will be appreciated!

I recommend using Parse-Swift from here: https://github.com/netreconlab/Parse-Swift, as oppose to the parse-community one. I'm one of the original developers of Pase-Swift (you can see this on the contributors list) and I don't support the parse-community version anymore. The netreconlab version is way ahead of the other in terms of bug fixes and features and you will get better and faster support from the netreconlab since I've been the one to address the majority of issues in the past.
What is the best way that I can pull in a relational column? Inside MGLocation, I have a related column called images which can access another object.
I always recommend looking at the Playgrounds for similar examples. The Playgrounds has Roles and Relation examples. Assuming you are using version 4.16.2 on netreconlab. The only way you can do this with your current relation on your server is like so:
// You need to add your struct for MGImage on your client
struct MGImage: ParseObject { ... }
struct MGLocation: ParseObject {
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var originalData: Data?
var ACL: ParseACL?
var title: String?
var category: String?
var images: ParseRelation<Self>? // Add this relation
/* These aren't needed as they are already given for free.
init() {}
init(objectId: String?) {
self.objectId = objectId
}
*/
}
//: It's recommended to place custom initializers in an extension
//: to preserve the memberwise initializer.
extension MGLocation {
// The two inits in your code aren't needed because ParseObject gives them to you
}
//: Now we will see how to use the stored `ParseRelation on` property in MGLocation to create query
//: all of the relations to `scores`.
Task {
do {
//: Fetch the updated location since the previous relations were created on the server.
let location = try await MGLocation(objectId: "myLocationObjectId").fetch()
print("Updated current location with relation: \(location)")
let usableStoredRelation = try location.relation(location.images, key: "images")
let images = try await (usableStoredRelation.query() as Query<MGImage>).find()
print("Found related images from stored ParseRelation: \(images)")
} catch {
print("\(error.localizedDescription)")
}
}
If your images column was of type [MGImage] instead of Relation<MGImage> then you would be able to use var images: [MGImage]? in your MGLocation model and simply use include on your query. You can see more here: https://github.com/netreconlab/Parse-Swift/blob/325196929fed80ca3120956f2545cf2ed980616b/ParseSwift.playground/Pages/8%20-%20Pointers.xcplaygroundpage/Contents.swift#L168-L173

Related

How to create many-to-many association using GRDB in Swift?

I'm trying to setup an association between songs and albums. Each song can appear on one or more albums and each album can contain one or more songs. I decided to go with GRDB for my database solution but I'm stuck on this issue.
What I tried:
As documentation suggests, I created a passport struct, like this:
public struct AlbumPassport: TableRecord {
static let track = belongsTo(SPTTrack.self)
static let album = belongsTo(SPTAlbum.self)
}
Then in SPTTrack class:
public static let passports = hasMany(AlbumPassport.self)
public static let albums = hasMany(SPTAlbum.self, through: passports, using: AlbumPassport.album)
And in SPTAlbum class:
public static let passports = hasMany(AlbumPassport.self)
public static let tracks = hasMany(SPTTrack.self, through: passports, using: AlbumPassport.track)
I cannot find in the documentation a good example on how to build a request using those associations. In SPTAlbum class I added linkedTracks property
public var linkedTracks: QueryInterfaceRequest<SPTTrack> {
request(for: Self.tracks)
}
And then in my database manager:
func fetchTracks(for album: SPTAlbum) -> [SPTTrack] {
do {
return try dbQueue.read { db in
try album.linkedTracks.fetchAll(db)
}
} catch {
print(error)
}
return []
}
I'm getting error:
SQLite error 1: no such table: albumPassport
which is pretty self-explanatory, but I have no clue how and where should I create table for the AlbumPassport struct and if there are any additional steps I should take to actually populate this table with album/track connections.
Both SPTTrack/SPTAlbum have a field called id which is set as primaryKey during first migration.
There are no issues with your associations. All hasMany() and belongsTo() are correct. The error you are getting tells me that there is something wrong with your database setup (which you didn't include in your question).
Here is how i would implement it:
import GRDB
struct Album: Codable, Hashable, FetchableRecord, MutablePersistableRecord {
mutating func didInsert(with rowID: Int64, for column: String?) { id = rowID }
var id: Int64?
var name: String
static let passports = hasMany(AlbumPassport.self)
static let tracks = hasMany(Track.self, through: passports, using: AlbumPassport.track)
}
struct Track: Codable, Hashable, FetchableRecord, MutablePersistableRecord {
mutating func didInsert(with rowID: Int64, for column: String?) { id = rowID }
var id: Int64?
var name: String
static let passports = hasMany(AlbumPassport.self)
static let albums = hasMany(Album.self, through: passports, using: AlbumPassport.album)
}
struct AlbumPassport: Codable, Hashable, FetchableRecord, PersistableRecord {
let track: Int64
let album: Int64
static let track = belongsTo(Track.self)
static let album = belongsTo(Album.self)
}
let queue = DatabaseQueue()
try queue.write { db in
try db.create(table: "album") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
}
try db.create(table: "track") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
}
try db.create(table: "albumPassport") { t in
t.column("track", .integer).notNull().indexed().references("track")
t.column("album", .integer).notNull().indexed().references("album")
t.primaryKey(["track", "album"])
}
// Testing real data from https://music.apple.com/de/artist/yiruma/73406786
var solo = Album(name: "SOLO")
try solo.insert(db)
var sometimes = Track(name: "Sometimes Someone")
try sometimes.insert(db)
try AlbumPassport(track: sometimes.id!, album: solo.id!).insert(db)
var destiny = Track(name: "Destiny Of Love")
try destiny.insert(db)
try AlbumPassport(track: destiny.id!, album: solo.id!).insert(db)
var bestOf = Album(name: "Best of Yiroma")
try bestOf.insert(db)
var poem = Track(name: "Poem")
try poem.insert(db)
try AlbumPassport(track: poem.id!, album: bestOf.id!).insert(db)
var river = Track(name: "River Flows In You")
try river.insert(db)
try AlbumPassport(track: river.id!, album: bestOf.id!).insert(db)
}
// Fetch all albums and their tracks
try queue.read { db in
struct AlbumInfo: FetchableRecord, Decodable, CustomStringConvertible {
var album: Album
var tracks: Set<Track>
var description: String { "\(album.name) → \(tracks.map(\.name))" }
}
let request = Album.including(all: Album.tracks)
let result = try AlbumInfo.fetchAll(db, request)
print(result)
// > [SOLO → ["Sometimes Someone", "Destiny Of Love"], Best of Yiroma → ["River Flows In You", "Poem"]]
}
See my answer of Many-to-many relationship where one entity has multiple properties of the same type for another code example.

Issue with adding Data to an AnyObject Var so that I could make native ads work

for postdata in postdata {
if index < tableViewItems.count {
tableViewItems.insert(postdata, at: index)
index += adInterval
} else {
break
}
}
I'll need to add both PostData ads and Native Ads on the same AnyObject Var for me to get it to work and I can't find a way to add the Post Data because it says an error appears saying "Argument type 'PostData' expected to be an instance of a class or class-constrained type." Assistance would be very much appreciated, thank you.
edit 1
class Ad {
var postimage: String!
var publisher: String!
var timestring: String!
var timestamp = Date().timeIntervalSince1970
}
class PostDataAd: Ad {
// Declare here your custom properties
struct PostData1
{
var postimage: String
var publisher: String
var timestring : String
var timestamp = Date().timeIntervalSince1970
}
}
class NativeAd: Ad {
// Declare here your custom properties
struct NativeAd
{
var nativeAds: [GADUnifiedNativeAd] = [GADUnifiedNativeAd]()
}
}
My model class to merge both Data into one AnyObject Var
and then trying to append the Data from Firebase by doing this
var ads: [Ad] = [PostDataAd(), NativeAd()]
let postList = PostDataAd.PostData1(postimage: postimage, publisher:
postpublisher, timestring: postid, timestamp: timestamp)
self.ads.insert(postList, at:0)
an error occurs saying Cannot convert value of type 'PostDataAd.PostData1' to expected argument type 'Ad'
I hope I got what you want correctly. So basically you have two objects which you want to store in one array, under AnyObject. If that is correct, I recommend you to go in a bit of different direction. It is a nice example where you can use subclassing. You can declare a class called Ad, where you define the common properties which will be true for both PostDataAds and NativeAds.
class Ad {
// Add here the common elements you want to retrieve from both classes
var name: String = ""
}
After you define your PostDataAds and NativeAds inheriting from Ad:
class PostDataAd: Ad {
// Declare here your custom properties
}
class NativeAd: Ad {
// Declare here your custom properties
}
And if you want to define an array with two types of objects you can go:
let ads: [Ad] = [PostDataAd(), NativeAd()]
When retrieving you can check their type:
if let item = ads.first as? PostDataAd {
// The Ad is a PostDataAd
} else if let item = ad as? NativeAd {
// The Ad is a NativeAd
}
Or at some cases you don't even how to know the exact type, as you can access the properties defined in Ad without checking.
Update:
First of all your PostData1 and Ad objects are the same, you don't need to duplicate them. If you really want to have two classes you can inherit PostData1 from Ad.
class Ad {
var postimage: String
var publisher: String
var timestring: String
var timestamp = Date().timeIntervalSince1970
// You can add timestamp here also if you wish
init(postimage: String, publisher: String, timestring: String) {
self.postimage = postimage
self.publisher = publisher
self.timestring = timestring
}
}
class PostDataAd: Ad {
// Define some custom properties
}
And if you want to append PostData to the [Ad] array, you would do the following:
var ads: [Ad] = []
// Replace with your values
let postList = PostDataAd(postimage: "", publisher: "", timestring: "")
ads.insert(postList, at: 0)
// Appending NativeAd works also
let nativeAdd = NativeAd(postimage: "", publisher: "", timestring: "")
ads.append(nativeAdd)

Swift detect in the setter which object in an array was changed

I have an array of Habit objects which I am modifying in memory and a getter/setter to read and write it to documents storage as JSON whenever changes are made. I have split each object into its own JSON file and so far everything works, however changing one object in the array will re-write all the files. I understand this is what I instructed my setter to do, but is there a way to only write the objects in newValue that were changed?
extension Storage {
static var habits: [Habit] {
get { ... }
set {
newValue.forEach({
let data = try! JSONEncoder().encode($0)
do { try data.write(to: Storage.habitsFolder.appendingPathComponent("habit-\($0.id).json")) }
catch let error {
print("Failed to write Habit \($0.id): \(error.localizedDescription)")
}
})
}
}
}
The way I would make a change now is Storage.habits[0].name = "Some name". Which calls the setter, which then re-writes the files for each habit. So I was wondering if there's some way to detect which part of newValue changed or pass an index to it and only update that file.
Is the only way to go about this to have the array be get-only and use a different method for setting each habit file?
Thank you, and apologies if this is a silly question.
Update: adding Habit class for more context
class Habit: Codable, CustomStringConvertible {
// MARK: - Properties
var id: Int
var name: String?
var notes: String?
var icon: String
var repeatPattern: RepeatPattern
var entries: [HabitEntry]
var reminders: [Reminder]
init(id: Int, name: String?, notes: String?, icon: String, entries: [HabitEntry], reminders: [Reminder]) {
self.id = id
self.name = name
self.notes = notes
self.icon = icon
self.repeatPattern = RepeatPattern(pattern: 0, startDay: calendar.today, enabledWeekdays: [1,2,3,4,5,6,7], expectedTimes: 1)
self.entries = entries
self.reminders = reminders
}
var description: String { return "Habit id: \(id), named: \(name ?? "nil"), \nnotes: \(notes ?? "nil"), \nicon: \(icon), \nrepeatPattern: \(repeatPattern), \nentries: \(entries), \nreminders: \(String(describing: reminders))" }
}
You could make a property to store the existing habits first
class Storage {
static var existingHabits = [Habit]()
}
Then, inside the set, see which Habits are new:
static var habits: [Habit] {
get { ... }
set {
var habitsToChange = [Habit]()
if existingHabits.count == 0 { /// it's empty, just write with newValue
habitsToChange = newValue
} else {
let differentIndicies = zip(existingHabits, newValue).enumerated().filter() {
$1.0 != $1.1
}.map{$0.0} /// from https://stackoverflow.com/a/30685226/14351818
for index in differentIndicies {
habitsToChange.append(newValue[index])
}
}
habitsToChange.forEach({ /// loop over the habits that have changed
do {
try JSONEncoder().encode($0).write(to: habitsFolder.appendingPathComponent("habit-\($0.id).json"))
} catch {
print("Failed to write Habit \($0.id): \(error)")
}
})
existingHabits = newValue /// set existing to the new value
}
}

How to remove optional text from json Result In swift

I am using newsApi to get list of news from it. I created model based on news's property, all property are optional in model and as i parse it printed to console getting result but all fields have data with optional text
I have created three struct based on news api fields, They are like
struct GoogleNews: Codable {
var status: String?
var totalResults: Int?
var articles: [Article]
}
struct Article: Codable {
var source: Source
var author: String?
var title: String?
var description: String?
var url: String?
var urlToImage: String?
var publishedAt: String?
var content: String?
}
struct Source: Codable {
var id: String?
var name: String?
}
Calling the appi
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {return}
do {
let allNews = try JSONDecoder().decode(GoogleNews.self, from: data)
print(allNews.articles[0])
} catch let error {
print(error.localizedDescription)
}
}.resume()
After calling the api, in result all fields are having result with optional text
name: Optional("Venturebeat.com")), author: Optional("Dean Takahashi"), title: Optional("How Paymentwall’s Terminal3 lets game developers create their own online shops"), description: Optional("Paymentwall built a business as a global payments platform, with much of its focus on games. Last year, the company spun out its Terminal3 as a platform for monetizing and distributing games. Now it is making it easier for indie, small, and medium-size game c…")...ect
What should be the solution to remove the optional text from the results..
For the values in your struct that are optional, be sure they're optionals because you know for sure that there are cases where a value won't be returned. If you'd like to unwrap them, you have 2 ways of doing so.
The first way is to use an if-let statement, which would look something like this:
if let name = allNews.articles[0].name {
}
Within the curly braces is where you would use the variable name, which wouldn't be the optional value you're asking about because it's been unwrapped.
The second method you could use is a guard statement, which looks like this:
guard let name = allNews.articles[0].name else { return }
In this instance, the name variable would be unwrapped and can be used anywhere in the scope of your code. However, it's only valid if it can be successfully unwrapped. If it cannot, then the return statement is called and breaks out of whatever scope it's in.
The thing is you're trying to print an object which all properties are optionals. You have to unwrap the properties even if you unwrap the article . You can unwrap your optionals like this:
let article = allNews.articles[0]
if let source = article.source {
print(source)
}
Also you can unwrap more than one property at once:
if let source = article.source, let author = article.author, let title = article.title {
// do things
}
Just remove the sign of ? from the end of the Struct members declaration to become like this:
let author: String

What is the best way to connect Realm and SwiftBond

I love Realm and I love Bond. Both of them makes app creation a joy. So I was wondering what is the best way to connect Realm and Bond? In Realm we can store basic types such as Int, String, e.g. But in Bond we work with Dynamics and Bonds. The only way that I found to connect Realm and Bond is following:
class TestObject: RLMObject {
dynamic var rlmTitle: String = ""
dynamic var rlmSubtitle: String = ""
var title: Dynamic<String>
var subtitle: Dynamic<String>
private let titleBond: Bond<String>!
private let subtitleBond: Bond<String>!
init(title: String, subtitle: String) {
self.title = Dynamic<String>(title)
self.subtitle = Dynamic<String>(subtitle)
super.init()
self.titleBond = Bond<String>() { [unowned self] title in self.rlmTitle = title }
self.subtitleBond = Bond<String>() { [unowned self] subtitle in self.rlmSubtitle = subtitle }
self.title ->> titleBond
self.subtitle ->> subtitleBond
}
}
But it surely lacks simplicity and elegance and produces a lot of boiler code. Is there any way to do this better?
With Realm supporting KVO and Bond 4, you can extend Realm objects to provide Observable variants. There is some boilerplate to it, but it's clean and without hacks.
class Dog: Object {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
}
extension Dog {
class ObservableDog {
let name: Observable<String>
let birthdate: Observable<NSDate>
init(dog: Dog) {
name = Observable(object: dog, keyPath: "name")
birthdate = Observable(object: dog, keyPath: "birthdate")
}
}
func observableVariant() -> Dog.ObservableDog {
return ObservableDog(dog: self)
}
}
Than you'll be able to do:
let myDog = Dog().observableVariant()
myDog.name.observe { newName in
print(newName)
}
myDog.name.bindTo(nameLabel.bnd_text)
realm.write {
myDog.name.value = "Jim"
}
You could likely simplify the pattern you're using somewhat if you used
default property values:
class TestObject: RLMObject {
dynamic var rlmTitle = ""
dynamic var rlmSubtitle = ""
var title: Dynamic<String>
var subtitle: Dynamic<String>
private let titleBond = Bond<String>() { [unowned self] title in self.rlmTitle = title }
private let subtitleBond = Bond<String>() { [unowned self] subtitle in self.rlmSubtitle = subtitle }
init(title: String, subtitle: String) {
self.title = Dynamic<String>(title)
self.subtitle = Dynamic<String>(subtitle)
self.title ->> titleBond
self.subtitle ->> subtitleBond
super.init()
}
}
You could remove another two lines of code if Bond's ->> operator returned the
left value so you could do self.title = Dynamic<String>(title) ->> titleBond.
But ultimately, until Swift has native language support for KVO or an equivalent observation mechanism, you're sadly going to have to write some amount of boilerplate.
I've been thinking about this for three days and came up with nearly perfect solution, which does not employ any boilerplate code. First of all I have created a super class for a realm model's wrapper:
class BondRealmBaseClass {
private var realmModel: RLMObject!
private let realm = RLMRealm.defaultRealm()
private var bonds = NSMutableArray()
init(){
realmModel = createRealmModel()
realm.beginWriteTransaction()
realm.addObject(realmModel)
realm.commitWriteTransaction()
createBonds()
}
init(realmModel: RLMObject){
self.realmModel = realmModel
createBonds()
}
func createBondFrom<T>(from: Dynamic<T>, toModelKeyPath keyPath: String){
from.value = realmModel.valueForKeyPath(keyPath) as T
let bond = Bond<T>() { [unowned self] value in
self.realm.beginWriteTransaction()
self.realmModel.setValue(value as NSObject, forKey: keyPath)
self.realm.commitWriteTransaction()
}
from ->| bond
bonds.addObject(bond)
}
//MARK: - Should be overriden by super classes
func createBonds(){ fatalError("should be implemented in supreclass") }
func createRealmModel() -> RLMObject{ fatalError("should be implemented in supreclass") }
}
After that for each realm model I create two classes, first is the actual realm model, which stores all properties:
class RealmTodoModel: RLMObject {
dynamic var title = ""
dynamic var date = NSDate()
}
and a second one is the wrapper around realm model:
class TodoModel : BondRealmBaseClass{
let title = Dynamic("")
let date = Dynamic(NSDate())
override func createBonds(){
createBondFrom(title, toModelKeyPath: "title")
createBondFrom(date, toModelKeyPath: "date")
}
override func createRealmModel() -> RLMObject { return RealmTodoModel() }
}
And this two classes is actually all is needed to link Realm and Bond: creating new TodoModel will actually add to Realm new RealmTodoModel and all changes made with TodoModel's title and date will be automatically saved to corresponding Realm model!
EDIT
I added some functionality and posted this as a framework on GitHub. Here is the link.

Resources