In my application I want to implement separate class to keep all the temporary variables of Now Playing item for Music Player.
It has lots of properties with different types, but they should be handled in the same way. They should be handled in the class method "updateData" (see the end of code)
This is my code:
struct DataDefaults {
//MARK: Default properties
let albumTitle: String? = "Unknown Album"
let albumArtist: String? = "Unknown Artist"
let title: String? = "Unknown Title"
let artist: String? = "Unknown Artist"
let artwork: UIImage? = UIImage(named: "noartwork")!
let genre: String? = ""
let lyrics: String? = "No Lyrics"
let releaseDate: Date? = nil
let playbackDuration: TimeInterval? = 0
let rating: Int? = 0
let assetURL: URL? = nil
let isExplicitItem: Bool? = false
let isCloudItem: Bool? = false
let hasProtectedAsset: Bool? = false
}
class SongInfo: NSObject {
static let sharedData = SongInfo()
let defaults = DataDefaults()
//MARK: Properties
var albumTitle: String
var albumArtist: String
var title: String
var artist: String
var artwork: UIImage
var genre: String
var lyrics: String
var releaseDate: Date?
var playbackDuration: TimeInterval
var rating: Int
var assetURL: URL?
var isExplicitItem: Bool
var isCloudItem: Bool
var hasProtectedAsset: Bool
//MARK: Init
private override init () {
self.albumTitle = defaults.albumTitle!
self.albumArtist = defaults.albumArtist!
self.title = defaults.title!
self.artist = defaults.artist!
self.artwork = defaults.artwork!
self.genre = defaults.genre!
self.lyrics = defaults.lyrics!
self.releaseDate = defaults.releaseDate
self.playbackDuration = defaults.playbackDuration!
self.rating = defaults.rating!
self.assetURL = defaults.assetURL
self.isExplicitItem = defaults.isExplicitItem!
self.isCloudItem = defaults.isCloudItem!
self.hasProtectedAsset = defaults.hasProtectedAsset!
}
//MARK: Set properties
func updateData(allData: DataDefaults) {
var wasUpdated: Bool = false
if allData.albumTitle == self.albumTitle {
//pass
} else if allData.albumTitle == nil || allData.albumTitle == "" {
self.albumTitle = defaults.albumTitle!
wasUpdated = true
} else {
self.albumTitle = allData.albumTitle!
wasUpdated = true
}
//Need to repeat same IF for all properties
}
}
Is there any way I can use property name to make some reusage of the same code instead of duplicating it?
Rather than trying to find a solution to a weird design, I re-designed for what you're trying to accomplish 🙂
struct SongData: Equatable {
static let defaultData = SongData(albumTitle: "Unknown Album",
albumArtist: "Unknown Artist",
title: "Unknown Title",
artist: "Unknown Artist",
artwork: UIImage(named: "noartwork"),
genre:"",
lyrics: "No Lyrics",
releaseDate: nil,
playbackDuration: 0,
rating: 0,
assetURL: nil,
isExplicitItem: false,
isCloudItem: false,
hasProtectedAsset: false)
//MARK: Default properties
var albumTitle: String?
var albumArtist: String?
var title: String?
var artist: String?
var artwork: UIImage?
var genre: String?
var lyrics: String?
var releaseDate: Date?
var playbackDuration: TimeInterval?
var rating: Int?
var assetURL: URL?
var isExplicitItem: Bool?
var isCloudItem: Bool?
var hasProtectedAsset: Bool?
/// This initializer will set the properties to the defaultData properties if a passed value is nil
init(albumTitle: String?, albumArtist: String?, title: String?, artist: String?, artwork: UIImage?, genre: String?, lyrics: String?, releaseDate: Date?, playbackDuration: TimeInterval?, rating: Int?, assetURL: URL?, isExplicitItem: Bool?, isCloudItem: Bool?, hasProtectedAsset: Bool?) {
// initialize properties where the default is nil
self.releaseDate = releaseDate
self.assetURL = assetURL
//initialize other properties with the passed values, or use the default value if nil
self.albumTitle = SongData.valueOrDefault(albumTitle, SongData.defaultData.albumTitle)
self.albumArtist = SongData.valueOrDefault(albumArtist, SongData.defaultData.albumArtist)
self.title = SongData.valueOrDefault(title, SongData.defaultData.title)
self.artist = SongData.valueOrDefault(artist, SongData.defaultData.artist)
self.artwork = artwork ?? SongData.defaultData.artwork
self.genre = SongData.valueOrDefault(genre, SongData.defaultData.genre)
self.lyrics = SongData.valueOrDefault(lyrics, SongData.defaultData.lyrics)
self.playbackDuration = playbackDuration ?? SongData.defaultData.playbackDuration
self.rating = rating ?? SongData.defaultData.rating
self.isExplicitItem = isExplicitItem ?? SongData.defaultData.isExplicitItem
self.isCloudItem = isCloudItem ?? SongData.defaultData.isCloudItem
self.hasProtectedAsset = hasProtectedAsset ?? SongData.defaultData.hasProtectedAsset
}
static func ==(leftItem: SongData, rightItem: SongData) -> Bool {
return (leftItem.albumTitle == rightItem.albumTitle) &&
(leftItem.albumArtist == rightItem.albumArtist) &&
(leftItem.title == rightItem.title) &&
// Comparing a reference type here. may need to be handled differently if that's a problem
(leftItem.artwork === rightItem.artwork) &&
(leftItem.genre == rightItem.genre) &&
(leftItem.lyrics == rightItem.lyrics) &&
(leftItem.releaseDate == rightItem.releaseDate) &&
(leftItem.playbackDuration == rightItem.playbackDuration) &&
(leftItem.rating == rightItem.rating) &&
(leftItem.assetURL == rightItem.assetURL) &&
(leftItem.isExplicitItem == rightItem.isExplicitItem) &&
(leftItem.isCloudItem == rightItem.isCloudItem) &&
(leftItem.hasProtectedAsset == rightItem.hasProtectedAsset)
}
//simple helper function to avoid long turneries in the init
static func valueOrDefault(_ value: String?, _ defaultValue: String?) -> String? {
guard let value = value, !value.isEmpty else {
return defaultValue
}
return value
}
}
class SongInfo {
static let sharedData = SongInfo()
var data: SongData
//MARK: Init
private init ()
{
self.data = SongData.defaultData
}
//MARK: Set properties
func updateData(newData: SongData) {
if(newData != self.data) {
self.data = newData
}
}
}
I changed your struct to act more like it appears you're wanting it to be used, and the struct's init will fall back to using the default values if the init values are nil. My design also contains no force unwraps, which are almost always bad.
You could set the defaults directly in your class definition without using a separate struct and have a static unaltered instance with the default values.
For example:
class SongInfo: NSObject {
static let sharedData = SongInfo()
static let defaults = SongInfo()
//MARK: Properties
var albumTitle: String? = "Unknown Album"
var albumArtist: String? = "Unknown Artist"
var title: String? = "Unknown Title"
var artist: String? = "Unknown Artist"
var artwork: UIImage? = UIImage(named: "noartwork")!
var genre: String? = ""
var lyrics: String? = "No Lyrics"
var releaseDate: Date? = nil
var playbackDuration: TimeInterval? = 0
var rating: Int? = 0
var assetURL: URL? = nil
var isExplicitItem: Bool? = false
var isCloudItem: Bool? = false
var hasProtectedAsset: Bool? = false
//MARK: Init
private override init ()
{
// nothing to do here
}
//MARK: Set properties
func updateData(allData: DataDefaults) {
var wasUpdated: Bool = false
if allData.albumTitle == self.albumTitle {
//pass
} else if allData.albumTitle == nil || allData.albumTitle == "" {
self.albumTitle = SongInfo.defaults.albumTitle!
wasUpdated = true
} else {
self.albumTitle = allData.albumTitle!
wasUpdated = true
}
//Need to repeat same IF for all properties
}
}
If you also need to manipulate the basic data without the whole class functionality, you could define a SongInfoData class with only the properties and make SingInfo inherit from that class. Then the static variable for defaults could be in the SongInfoData class and the SingInfo subclass wouldn't need any property declarations.
[EDIT] avoiding code repetition in update function ...
You can generalize the property update process by adding a generic function to your class:
For example:
func assign<T:Equatable>(_ variable:inout T?, _ getValue:(SongInfo)->T?) -> Int
{
let newValue = getValue(self)
if variable == newValue
{ return 0 }
var valueIsEmpty = false
if let stringValue = newValue as? String, stringValue == ""
{ valueIsEmpty = true }
if newValue == nil || valueIsEmpty
{
variable = getValue(SongInfo.defaults)
return 1
}
variable = newValue
return 1
}
func update(with newInfo:SongInfo)
{
let updates = newInfo.assign(&albumTitle) {$0.albumTitle}
+ newInfo.assign(&albumArtist) {$0.albumArtist}
+ newInfo.assign(&title) {$0.title}
+ newInfo.assign(&artist) {$0.artist}
+ newInfo.assign(&artwork) {$0.artwork}
+ newInfo.assign(&genre) {$0.genre}
+ newInfo.assign(&lyrics) {$0.lyrics}
// ...
if updates > 0
{
// react to update
}
}
It seems to me, that you're using MPMedia item.
If so, you don't have to store all these properties at all.
You just need to store persistent ID of the item (convert from UInt64 to string), and later fetch MPMediaItem by using MPMediaQuery with predicate, something like this:
func findSong(persistentIDString: String) -> MPMediaItem? {
let predicate = MPMediaPropertyPredicate(value: persistentIDString, forProperty: MPMediaItemPropertyPersistentID)
let songQuery = MPMediaQuery()
songQuery.addFilterPredicate(predicate)
return songQuery.items.first
}
Related
So I inherited this code. But I have a Teams class and I have a copy function. Let me show you the class first:
class Team: NSObject, NSCopying {
struct Keys {
static let sportsID = "SportsID"
static let stampId = "StampNumber"
static let associationID = "AssociationID"
static let team = "Team"
static let conference = "Conference"
static let latitude = "Latitude"
static let longitude = "Longitude"
static let GPSLatitude = "GpsLatitude"
static let GPSLongitude = "GpsLongitude"
static let backgroundColor = "BackgroundColor"
static let letterColor = "LetterColor"
static let isActive = "IsActive"
static let venueLocation = "VenueLocation"
static let city = "City"
static let state = "State"
static let stadiumCampus = "StadiumCampus"
static let letterName = "LetterName"
static let backgroundImage = "BackgroundImage"
static let homeStampImag = "HomeStamp"
}
var objectId: String = ""
var sportsID: Int = -1
var associationID: Int = -1
var stampId: Int = -1
var team: String = ""
var conference: String = ""
var stadiumCampus: String = ""
var latitude: Double = 0.0
var longitude: Double = 0.0
var radius: Double = 100
var backgroundColorString: String = ""
var letterColor: String = ""
var letterName: String = ""
var isActive: Bool = false
var venueLocation: String = ""
var city: String = ""
var state: String = ""
var backgroundImageId: Int = 1
var backgroundImage: UIImage?
var homeStampImag: PFFileObject?
var stampImage: UIImage?
var rotationAngle: CGFloat = CGFloat.random(in: -120...160)
var date: String = ""
var timestamp: Date = Date()
var data: [String: Any] = [:]
var isHomeTeam: Bool = false
init(data: [String: Any] = [:], radius: Double, objectId: String = "") {
super.init()
self.objectId = objectId
self.sportsID = data[Keys.sportsID] as? Int ?? -1
self.stampId = data[Keys.stampId] as? Int ?? -1
self.date = UserDefaults.standard.string(forKey: "StampIdDate: \(self.stampId)") ?? ""
self.associationID = data[Keys.associationID] as? Int ?? -1
self.team = data[Keys.team] as? String ?? ""
self.conference = data[Keys.conference] as? String ?? ""
self.stadiumCampus = data[Keys.stadiumCampus] as? String ?? ""
self.latitude = (data[Keys.latitude] as? Double ?? 0.0) == 0.0 ? data[Keys.GPSLatitude] as? Double ?? 0.0 : data[Keys.latitude] as? Double ?? 0.0
self.longitude = (data[Keys.longitude] as? Double ?? 0.0) == 0.0 ? data[Keys.GPSLongitude] as? Double ?? 0.0 : data[Keys.longitude] as? Double ?? 0.0
self.letterName = data[Keys.letterName] as? String ?? ""
self.letterColor = data[Keys.letterColor] as? String ?? ""
self.backgroundColorString = data[Keys.backgroundColor] as? String ?? ""
self.isActive = data[Keys.isActive] as? Bool ?? false
self.venueLocation = data[Keys.venueLocation] as? String ?? ""
self.city = data[Keys.city] as? String ?? ""
self.state = data[Keys.state] as? String ?? ""
self.radius = radius
self.backgroundImageId = data[Keys.backgroundImage] as? Int ?? 1
guard let stampImage = data[Keys.homeStampImag] as? PFFileObject else { return }
self.homeStampImag = stampImage
if self.venueLocation.isEmpty {
self.venueLocation = self.team
}
if self.stadiumCampus.isEmpty {
self.venueLocation = self.city + ", " + self.state
}
self.data = data
}
func copy(with zone: NSZone? = nil) -> Any {
let copy = Team(data: data, radius: radius, objectId: objectId)
return copy
}
}
I init a team
let team = Team(data: teamJson, radius: sport.radius, objectId: sportTeam.objectId ?? "")
then later on I copy that team
let newTeam = team.copy() as! Team
So im still learning this stuff but I would assume team and newTeam would be the same. But newTeam is all empty.
<Pass_Sports.Team: 0x105fdb4e0> #0
- super: NSObject
- objectId: "iY012BOok2"
- sportsID: -1
- associationID: -1
- stampId: -1
- team: ""
- conference: ""
- stadiumCampus: ""
- latitude: 0.0
- longitude: 0.0
- radius: 100.0
- backgroundColorString: ""
- letterColor: ""
- letterName: ""
- isActive: false
- venueLocation: ""
- city: ""
- state: ""
- backgroundImageId: 1
- backgroundImage: nil
- homeStampImag: nil
- stampImage: nil
- rotationAngle: -31.274499700732946
- date: ""
â–¿ timestamp: 2021-11-30 07:38:43 +0000
- timeIntervalSinceReferenceDate: 659950723.884164
- data: 0 key/value pairs
- isHomeTeam: false
Please let me know if I need to give you anything else to figure this out. BTW I did dump teamJson and it has all the elements filled. And as I said, if I dump team before the copy, all the data is there. Especially in the data attribute. But after the copy, thats all default data.
Thanks
Just write a private initializer from another object of your type and use that in the copy(with:) implementation:
private init(other: Self) {
self.sportsID = other.sportsID
// etc
super.init()
}
func copy(with zone: NSZone? = nil) -> Any {
Self(other: self)
}
PS: I don’t know if the objectId property has a policy in your code that should make the copy differ from the original or not, thus take that into account too.
PPS: your initializer has a guard statement that will return before the data property is set too, and you are calling super.init() before setting the properties. Thus your init could just be able to set the whole object to default values, hence it could never set the data property to a non empty value because of that early exit with the guard statement. Since I don’t know if modifying your init will introduce any side effects in other parts of your code, I suggested to use the additional private initializer that will be used by copy(with)only, instead of fixing your original init.
i have two models (User and Project) as below:
class User: Object { // Users that logged in app in specific device atleast once
#objc dynamic var serverId: Int = 0
#objc dynamic var firstName: String = ""
#objc dynamic var lastName: String = ""
#objc dynamic var email: String?
#objc dynamic var company: String?
#objc dynamic var phoneNumber: String = ""
#objc dynamic var syncBaseTime: String = ""
let projects = LinkingObjects(fromType: Project.self, property: "user")
convenience init(_serverId: Int, _firstName: String, _lastName: String, _phoneNumber: String) {
self.init()
self.serverId = _serverId
self.firstName = _firstName
self.lastName = _lastName
self.phoneNumber = _phoneNumber
}
override static func primaryKey() -> String? {
return "serverId"
}
}
class Project: Object {
#objc dynamic var serverId: Int = 0
#objc dynamic var name: String = ""
#objc dynamic var stateId: Int = 0
#objc dynamic var stateName: String = ""
#objc dynamic var cityId: Int = 0
#objc dynamic var cityName: String = ""
#objc dynamic var user: User?
#objc dynamic var isOwner: Bool = false
#objc dynamic var isActive: Bool = false
#objc dynamic var compoundKey: String = ""
#objc dynamic var syncDetailTime: String = ""
let accountTitles = LinkingObjects(fromType: AccountTitle.self, property: "project")
let notes = LinkingObjects(fromType: Note.self, property: "project")
convenience init(_serverId: Int, _name: String, _stateId: Int, _stateName: String,
_cityId: Int, _cityName: String, _user: User, _isOwner: Bool, _isActive: Bool) {
self.init()
self.serverId = _serverId
self.name = _name
self.stateId = _stateId
self.stateName = _stateName
self.cityId = _cityId
self.cityName = _cityName
self.user = _user
self.isOwner = _isOwner
self.isActive = _isActive
self.compoundKey = "\(self.user!.serverId)-\(self.serverId)"
}
override static func primaryKey() -> String? {
return "compoundKey"
}
}
the problem occurs when I want to execute this query on them and get projects that current user working on them, but they are not currently choosen:
self.realm.objects(Project.self).filter(NSPredicate(format: "user.serverId == %# && isActive == true && serverId != %#", self.currentUser.serverId, self.currentProject.serverId))
I get this error with no more information from Xcode:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x11)
and I could not find where is my mistake and I will appreciate any help with this.
You are using the wrong specifier in the format string, %# is only for objects, and Int is not an object in terms of NSPredicate
Use %ld for Int
self.realm.objects(Project.self).filter(NSPredicate(format: "user.serverId == %ld && isActive == true && serverId != %ld", self.currentUser.serverId, self.currentProject.serverId))
Note: This is Swift. Please don't use ugly objective-c-ish variable names with starting underscore characters. self.name = name does compile.
I encounter a very strange bug, I have a PodEvent type array, and with a second function I get another array that I cast, when I make a print of the casted array with the result it shows me all the objects of the board...
PodEvent {
eventID = 2;
podID = 1;
drinkDate = 2018-09-25 10:00:00 +0000;
actualDrinkingTime = (null);
keyDate = 2018-09-25 13:00;
rawState = Forgotten;
}
But when I want to access the value of the object it returns to me as the default values! for example:
print(self.podEvents[1].eventID)
print(self.podEvents[1].podID)
outpout:
-1
-1
Here my class:
class PodEvent: Object {
#objc var eventID: Int = -1
#objc var podID: Int = -1
#objc var drinkDate: Date = Date()
#objc var actualDrinkingTime: Date? = Date()
var state: PodState = .future
#objc var keyDate: String = ""
#objc private var rawState: String!
convenience init(eventID: Int, podID: Int, drinkDate: Date, actualDrinkingTime: Date?, state: PodState){
self.init()
self.eventID = eventID
self.podID = podID
self.drinkDate = drinkDate
self.actualDrinkingTime = actualDrinkingTime
self.state = state
self.rawState = state.state
}
func setState(state: PodState){
self.state = state
rawState = state.state
}
override class func primaryKey() -> String? {
return "keyDate"
}
This bug is very strange
My code to fetch my array:
//Fetch pod history in internal database
self.databaseManager.fetch(object: PodEvent.self, predicate: nil, sortedBy: "keyDate", ascending: true) { success, results, error in
guard error == nil else {
Alerts.alertMessage(for: self, title: "ERROR".localized,
message: error!.localizedDescription,
closeHandler: nil)
return
}
self.podEvents = (results as! [PodEvent])
self.pods = pods
print(self.podEvents[1].eventID)
print(self.podEvents[1].podID)
}
and:
func fetch(object: Object.Type, predicate: NSPredicate?, sortedBy: String, ascending: Bool, completion: DatabaseCompletion?) {
do {
let realm = try Realm()
let objects: Results<Object>!
if let predicate = predicate {
objects = realm.objects(object).filter(predicate).sorted(byKeyPath: sortedBy, ascending: ascending)
} else {
objects = realm.objects(object).sorted(byKeyPath: sortedBy, ascending: ascending)
}
let objectsArray = Array(objects)
completion?(true, objectsArray, nil)
} catch let error {
print("Could not write object (type \(object)) to Realm:", error.localizedDescription)
completion?(false, nil, error)
}
}
im using realm
Your object definition is flawed, you need to add the dynamic keyword to all persisted properties.
class PodEvent: Object {
#objc dynamic var eventID = -1
#objc dynamic var podID = -1
#objc dynamic var drinkDate = Date()
#objc dynamic var actualDrinkingTime: Date? = Date()
var state: PodState = .future
#objc dynamic var keyDate = ""
#objc dynamic private var rawState: String!
...
}
I have a list of JSON data downloaded from server:
(DataModal.swift)
class DataModal {
var orderAutoid: Int?
var orderId: String?
var orderName: String?
var orderQty: String?
var orderStatus: String?
init(bOrder_autoid: Int, bOrder_id: String, bOrder_name: String, bOrder_qty: String, bOrder_status: String){
self.orderAutoid = bOrder_autoid
self.orderId = bOrder_id
self.orderName = bOrder_name
self.orderQty = bOrder_qty
self.orderStatus = bOrder_status
}
(OrderStructureDownloadProtocol.swift)
protocol OrderStructureDownloadProtocol: class {
func newItemDownload(items: Array<Any>)
}
....
var jsonElement = Dictionary<String, Any>()
var newOrders = Array<Any>()
for i in 0..<jsonResult.count {
jsonElement = jsonResult[i] as! Dictionary
let newOrder_autoid = jsonElement["orderAutoid"] as? Int ?? 0
let newOrder_id = jsonElement["orderId"] as? String ?? ""
let newOrder_name = jsonElement["orderName"] as? String ?? ""
let newOrder_qty = jsonElement["orderQty"] as? String ?? ""
let newOrder_status = jsonElement["orderStatus"] as? String ?? ""
let newOrder = BMSDataModal(bOrder_autoid: newOrder_autoid, bOrder_id: newOrder_id, bOrder_name: newOrder_name, bOrder_qty: newOrder_qty, bOrder_status: newOrder_status)
newOrders.append(newOrder)
}
DispatchQueue.main.async (
execute: { () -> Void in
self.delegate.newItemDownload(items: newOrders as! Array<Any>)
})
(tableview.swift)
var newOrdersArray = [BMSDataModal]()
func newItemDownload(items: Array<Any>) {
newOrdersArray = items as! [BMSDataModal]
newOrderLookupTableView.reloadData()
}
(tableview.swift another part)
let cell = tableView.dequeueReusableCell(withIdentifier: "orderLookupCell", for: indexPath) as! NewOrderTableViewCell
let item = newOrdersArray[indexPath.row]
cell.newHMNumber?.text = item.orderId ?? "-"
cell.newMP?.text = item.orderName ?? "-"
cell.newQTY?.text = item.orderQty ?? "-"
return cell
}
having all the old NS-style changed. The app is running okay, there are some items that need to reset. As my data-source always contain Double, but I declared it as a String, as I won't deal with calculation so I treated it as 'String'.
so I have two objects like the following :
class BankModel: Object {
dynamic var bankModelId: String = ""
dynamic var bankName: String!
dynamic var bankBranch: String?
var coordinate: List<LocationModel>!
dynamic var buyingTWD: String?
dynamic var buyingUSD: String?
dynamic var sellingTHB: String?
dynamic var sellingUSD: String?
override static func primaryKey() -> String? {
return "bankModelId"
}
}
and
class LocationModel: Object {
dynamic var locationId: String = ""
dynamic var latitude: String = ""
dynamic var longitude: String = ""
override static func primaryKey() -> String? {
return "locationId"
}
}
when I try to append LocationModel
let realm = try! Realm()
let bankModel = BankModel()
let coordinate = LocationModel()
coordinate.locationId = "1"
coordinate.longitude = "20.11111"
coordinate.latitude = "123.21412"
bankModel.bankModelId = "1"
bankModel.bankName = "SuperRich Orange"
bankModel.bankBranch = "HQ"
bankModel.buyingUSD = "\(buyingUSDRepalced)"
bankModel.buyingTWD = "\(buyingTWDRepalced)"
bankModel.coordinate.append(coordinate)
try! realm.write {
realm.add(bankModel, update: true)
}
I got this fatal error:
unexpectedly found nil while unwrapping an Optional value
No clue why is this happening, any hint is appreciated!
The coordinate is nil. You haven't initialized it. The compiler doesn't complain because you declared it as an implictly unwrapped optional, which can be nil. Use
var coordinate = List<LocationModel>()
instead.