Swift | Set with NSObject - swift3

I'm trying to create a Set with custom objects.
This is working, If I use a Set of my custom objects there is no duplicates :
public class AttributesGroup: Hashable, Equatable, Comparable {
open var id: Int!
open var name: String!
open var position: Int!
public init (id: Int = 0, name: String = "", position: Int = 0) {
self.id = id
self.name = name
self.position = position
}
open var hashValue: Int {
get {
return id.hashValue
}
}
public static func ==(lhs: AttributesGroup, rhs: AttributesGroup) -> Bool {
return lhs.id == rhs.id
}
public static func < (lhs: AttributesGroup, rhs:AttributesGroup) -> Bool {
return lhs.position < rhs.position
}
}
I extend my class with NSObject, since NSObject already implements Hashable protocol (and also Equatable) I have to override hashValue, and this is not working anymore, If I use a Set of my custom objects there is duplicates, what do I do wrong here ? :
public class AttributesGroup: NSObject, Comparable {
open var id: Int!
open var name: String!
open var position: Int!
public init (id: Int = 0, name: String = "", position: Int = 0) {
self.id = id
self.name = name
self.position = position
}
open override var hashValue: Int {
get {
return id.hashValue
}
}
public static func ==(lhs: AttributesGroup, rhs: AttributesGroup) -> Bool {
return lhs.id == rhs.id
}
public static func < (lhs: AttributesGroup, rhs:AttributesGroup) -> Bool {
return lhs.position < rhs.position
}
}
Thanks for your help !

NSObject is a Cocoa type. The rules for NSObject are different from the rules for Swift. To make an NSObject work in a set, it must have an implementation of isEqual consonant with its implementation of hash.

Related

Heterogeneous collection

In the new versions of C++, you can check if an item is in a unordered_set (a HashSet), even if that item is not the same type as the unordered_set, whilst maintaining O(1) time complexity.
I'm trying to find out how to do this in Swift.
Here is the C++ example:
struct First {
int data;
std::string otherData;
First(int data, std::string otherData) : data(data), otherData(otherData) { }
};
struct Second {
int data;
int otherData;
Second(int data, int otherData) : data(data), otherData(otherData) { }
};
Suppose I want to create an unordered_set of First, but I want to check if a Second object is in the Set, comparing by its data field. You could do this:
struct Equal {
using is_transparent = void;
template<class F, class S>
bool operator()(const F& lhs, const S& rhs) const {
return lhs.data == rhs.data;
}
};
struct Hash {
using is_transparent = void;
template<class T>
size_t operator()(const T& t) const {
return std::hash<int>{}(t.data);
}
};
int main()
{
std::unordered_set<First, Hash, Equal> set;
set.insert(First(100, "test"));
std::cout << set.contains(First(100, "bla")) << "\n"; // true
std::cout << set.contains(Second(100, 1000)) << "\n"; // true
}
And this works great. However, I'm not sure how you would achieve this in Swift. In Swift, a Set is the same thing as unordered_set, but its contains method only accepts that specific element (no overloads).
You could iterate through all the elements, but you lose the O(1) HashSet time complexity.
I was wondering, is this possible in Swift?
To meet the basic requirement (partial matching), you can use contains(where:) with a predicate to compare the hash values of elements to the hash of the target.
class First:Hashable {
var data:Int;
var otherData:String;
static func == (lhs:First, rhs:First) -> Bool {
return lhs.data == rhs.data;
}
init(data:Int, otherData:String) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
class Second:Hashable {
var data:Int;
var otherData:Int;
static func == (lhs:Second, rhs:Second) -> Bool {
return lhs.data == rhs.data;
}
init(data:Int, otherData:Int) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
var set: Set = [First(data: 100, otherData: "test")];
print(set.contains(First(data: 100, otherData: "bla")));
var hasher = Hasher();
Second(data: 100, otherData: 1000).hash(into:&hasher);
var target = hasher.finalize();
print(set.contains(where: {(candidate:First) -> Bool in
var hasher = Hasher();
candidate.hash(into:&hasher);
return hasher.finalize() == target;
}));
To meet the performance requirement, there are (at least) two options: refactor the hashable data to a common base class, or write an extension method that creates a temporary element of the appropriate type with the hashable data.
Moving the hashable data to a base class is the most straight-forward, though the resultant Set will only be homogenous in the base class. Also, this approach can't be implemented if you don't have control over the source of the element classes.
Once the classes are defined, Set.contains(_:) will work as desired.
class Zeroth:Hashable {
var data:Int;
static func == (lhs:Zeroth, rhs:Zeroth) -> Bool {
return lhs.data == rhs.data;
}
init(_ data:Int) {
self.data = data;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
class First:Zeroth {
var otherData:String;
init(data:Int, otherData:String) {
self.otherData = otherData;
super.init(data)
}
};
class Second:Zeroth {
var otherData:Int;
init(data:Int, otherData:Int) {
self.otherData = otherData;
super.init(data)
}
};
var test = First(data: 100, otherData: "test");
var bla = First(data: 100, otherData: "bla");
var set: Set<Zeroth> = [test];
print(set.contains(bla));
var member = Second(data: 100, otherData: 1000);
print(set.contains(member));
An extension method gets the closest to the C++ interface. Use a protocol so the extension method can be constrained to classes that only hash some of their data. The protocol used below also adds a method, partialCopy(from:), that handles converting between classes.
protocol DataElement {
var data:Int {get}
init(_ data:Int)
static func partialCopy<Other:DataElement>(from other:Other) -> Self;
}
extension DataElement {
static func partialCopy<Other:DataElement>(from other:Other) -> Self {
return Self(other.data);
}
}
class First:Hashable, DataElement {
var data:Int;
var otherData:String = "";
static func == (lhs:First, rhs:First) -> Bool {
return lhs.data == rhs.data;
}
required init(_ data:Int) {
self.data = data;
}
init(data:Int, otherData:String) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
class Second:Hashable, DataElement {
var data:Int;
var otherData:Int = 0;
static func == (lhs:Second, rhs:Second) -> Bool {
return lhs.data == rhs.data;
}
required init(_ data:Int) {
self.data = data;
}
init(data:Int, otherData:Int) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
var test = First(data: 100, otherData: "test");
var bla = First(data: 100, otherData: "bla");
var set: Set<First> = [test];
print(set.contains(bla));
extension Set where Element:DataElement {
func contains<Other:DataElement>(matching member:Other) -> Bool {
let matching : Element = Element.partialCopy(from:member); //Element(member.data);
return self.contains(matching);
}
}
var other = Second(data: 100, otherData: 1000);
print(set.contains(matching:other));
Method #1
You can use an enum to store First and Second in the same set. You will have a case for First and a case for Second.
In the Hashable conformance for the enum, you should hash the data which is the same between both structs. The Equatable conformance just makes sure that if the hashes are equal, they are equivalent, even if the enum case is different.
Example:
enum Both: Hashable {
case first(First)
case second(Second)
func hash(into hasher: inout Hasher) {
switch self {
case .first(let first):
hasher.combine(first.data)
case .second(let second):
hasher.combine(second.data)
}
}
static func == (lhs: Both, rhs: Both) -> Bool {
lhs.hashValue == rhs.hashValue
}
}
struct First {
let data: Int
let otherData: String
}
struct Second {
let data: Int
let otherData: Int
}
let set: Set<Both> = [.first(First(data: 100, otherData: "test"))]
let first = First(data: 100, otherData: "bla")
print(set.contains(.first(first))) // true
let second = Second(data: 100, otherData: 1000)
print(set.contains(.second(second))) // true
Method #2
This may not be possible, if First and Second must be a struct. However, if they don't, you can have a superclass that does the Hashable conformance.
Example:
class Superclass: Hashable {
let data: Int
init(data: Int) {
self.data = data
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
static func == (lhs: Superclass, rhs: Superclass) -> Bool {
lhs.data == rhs.data
}
}
class First: Superclass {
let otherData: String
init(data: Int, otherData: String) {
self.otherData = otherData
super.init(data: data)
}
}
class Second: Superclass {
let otherData: Int
init(data: Int, otherData: Int) {
self.otherData = otherData
super.init(data: data)
}
}
let set: Set<Superclass> = [First(data: 100, otherData: "test")]
let first = First(data: 100, otherData: "bla")
print(set.contains(first)) // true
let second = Second(data: 100, otherData: 1000)
print(set.contains(second)) // true

iOS 14 Widgets + Realm + SwiftUI 2.0?

I use Realm + SwiftUI 2.0, iOS 14. I successfully linked (read/write) Realm to Swift 2.0 iOS app.
final class DataEntryStore: ObservableObject {
private var entryCancellable: AnyCancellable?
private(set) var entryDB = DataObservable<Entry>()
// could store related references to other related DataObservables
#Published private(set) var entries: [Entry] = []
// MARK: - init
init() {
entryDB = DataObservable<Entry>()
entryCancellable = entryDB.$items.assign(to: \.entries, on: self)
}
}
class RealmEntry: Object, UUIDIdentifiable {
#objc dynamic var id: String = UUID().uuidString
#objc dynamic var entryType = ""
#objc dynamic var entryDate = Date()
#objc dynamic var note: String?
#objc dynamic var trainingType: String?
#objc dynamic var trainingTime = 0
override static func primaryKey() -> String? {
return "id"
}
}
// MARK: - Abstracted Data Struct which is what is presented with the UI
// UI Model
struct Entry: Hashable, RealmConvertible {
typealias RealmType = RealmEntry
// MARK: - Properties
var id: String
var entryType: DataEntryType
var entryDate: Date
var note: String?
var trainingType: TrainingType?
var trainingTime: Int
func toRealmObject() -> RealmEntry {
let realmObj = RealmEntry()
realmObj.id = id
realmObj.entryType = entryType.rawValue
realmObj.entryDate = entryDate
realmObj.note = note
if let trainingTypeValue = trainingType?.rawValue {
realmObj.trainingType = trainingTypeValue
}
realmObj.trainingTime = trainingTime
return realmObj
}
static func fromRealmObject(_ obj: RealmEntry) -> Entry {
Entry(obj)
}
init() {
self.id = UUID().uuidString
self.entryType = .note
self.entryDate = Date()
self.trainingTime = 0
}
init(_ obj: RealmEntry) {
self.id = obj.id
self.entryType = DataEntryType(fromRawValue: obj.entryType)
self.entryDate = obj.entryDate
self.note = obj.note
if let trainingType = obj.trainingType {
self.trainingType = TrainingType(fromRawValue: trainingType)
}
self.trainingTime = obj.trainingTime
}
}
protocol StringIdentifiable {
var id: String { get }
}
protocol UUIDIdentifiable: Identifiable { var id: String { get } }
//protocol Initializable { init() }
// MARK: - Map Between the Two
protocol RealmConvertible where Self: Equatable & UUIDIdentifiable {
associatedtype RealmType: Object & UUIDIdentifiable
func toRealmObject() -> RealmType
init(_ dest: RealmType)
}
// Dynamic Realm Binding for live data editing
extension RealmConvertible {
func realmBinding() -> Binding<Self> {
let h = RealmHelper()
return Binding<Self>(get: {
if let r = h.get(self.toRealmObject()) {
// get the latest realm version for most uptodate data and map back to abstracted structs on init
return Self(r)
} else {
// otherwise return self as it's the most uptodate version of the data struct
return self
}
}, set: h.updateConvertible)
}
}
However, I am having an issue when tried to link (read) it to Widget Extension.
#main
struct MyWidget: Widget {
let kind = "MyWidget"
var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
WidgetView()
}
.supportedFamilies([.systemSmall, .systemMedium])
.configurationDisplayName("My Widget")
.description("This is widget.")
}
}
struct WidgetView: View {
#EnvironmentObject var data: DataEntryStore
var body: some View {
Text(data.getRealmData)
ViewWithRealmDataFromMainApp()
}
}
I have tried to follow these instructions by using "App Groups" and Shared Objects to Main iOS App and to Widget Extension, but it didn't work.
More than that, if I link it to Realm, I can't even show static data on the widget. It shows blurry rectangles.
How to show data from the Realm on the iOS 14 widget?
https://i.stack.imgur.com/yzUG1.jpg
https://i.stack.imgur.com/nKy6i.jpg

Nested Struct models not causing view to re-render SwiftUI

I have a view that listens to a Model via and ObservableObject:
class Feed : ObservableObject {
// Posts to be displayed
#Published var posts = [Posts]()
...
...
}
And the Posts model looks like:
struct Posts: Hashable, Identifiable {
let bar: Bars
let time: String
var description: String
let id: String
let createdAt : String
let tags : [Friends]
let groups : [String]
var intializer : Friends // Creator of the post
}
Which contains multiple other Struct models like Friends and Bars. However, when I do change a value within one of these other models, it doesn't trigger the #Published to fire, so the view isn't redrawn. For example, the Friends model looks like:
struct Friends : Hashable {
static func == (lhs: Friends, rhs: Friends) -> Bool {
return lhs.id == rhs.id
}
let name: String
let username: String
let id : String
var thumbnail : UIImage?
var profileImgURL : String?
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
but when I change the thumbnail, the views are not redrawn. But when I change something directly apart of the Posts model, like the description attribute, the view is redrawn. How am I able to have the view redraw when the underlying model values are changed?
I change the thumbnail as shown:
// Grab the thumbnail of user (if exists)
if post.intializer.profileImgURL != nil {
AF.request(post.intializer.profileImgURL!, method: .get, encoding: URLEncoding.default)
.validate()
.responseData { (response) in
if let data = response.value {
// Find the index of where this post is in the array and set the profile img
if let indexOfPost = self.posts.firstIndex(of: post) {
self.posts[indexOfPost].intializer.thumbnail = UIImage(data: data)
}
}
}
}
But if I were to change the description doing the same thing:
// Grab the thumbnail of user (if exists)
if post.intializer.profileImgURL != nil {
AF.request(post.intializer.profileImgURL!, method: .get, encoding: URLEncoding.default)
.validate()
.responseData { (response) in
if let data = response.value {
// Find the index of where this post is in the array and set the profile img
if let indexOfPost = self.posts.firstIndex(of: post) {
self.posts[indexOfPost].description = "Loaded!!!!"
}
}
}
}
And when I do this, the view does update and change. I can see that the thumbnails are being loaded correctly, too, because I can print out the data sent, and sometimes the thumbnails are redrawn for the view correctly.
EDIT
As suggested I tried adding a mutating func to the struct:
struct Posts: Hashable, Identifiable {
let bar: Bars
let time: String
var description: String
let id: String
let createdAt : String
let tags : [Friends]
let groups : [String]
var intializer : Friends // Creator of the post
mutating func addInitThumbnail(img : UIImage) {
self.intializer.thumbnail = img
}
}
and then using it:
func grabInitThumbnail(post : Posts) {
// Grab the thumbnail of user (if exists)
if post.intializer.profileImgURL != nil {
AF.request(post.intializer.profileImgURL!, method: .get, encoding: URLEncoding.default)
.validate()
.responseData { (response) in
if let data = response.value {
// Find the index of where this post is in the array and set the profile img
if let indexOfPost = self.posts.firstIndex(of: post) {
if let thumbnailImg = UIImage(data: data) {
self.posts[indexOfPost].addInitThumbnail(img: thumbnailImg)
}
}
}
}
}
}
but it did not work either.
However, when I do:
func grabInitThumbnail(post : Posts) {
// Grab the thumbnail of user (if exists)
if post.intializer.profileImgURL != nil {
AF.request(post.intializer.profileImgURL!, method: .get, encoding: URLEncoding.default)
.validate()
.responseData { (response) in
if let data = response.value {
// Find the index of where this post is in the array and set the profile img
if let indexOfPost = self.posts.firstIndex(of: post) {
self.posts[indexOfPost].intializer.thumbnail = UIImage(data: data)
self.posts[indexOfPost].description = "Loaded!!!!"
}
}
}
}
}
the images are loaded and set correctly...? So I think it might have something to do with UIImages directly?
I tried using mutating function and also updating value directly, both cases it worked.
UPDATED CODE (Added UIImage in new struct)
import SwiftUI
import Foundation
//Employee
struct Employee : Identifiable{
var id: String = ""
var name: String = ""
var address: Address
var userImage: UserIcon
init(name: String, id: String, address: Address, userImage: UserIcon) {
self.id = id
self.name = name
self.address = address
self.userImage = userImage
}
mutating func updateAddress(with value: Address){
address = value
}
}
//User profile image
struct UserIcon {
var profile: UIImage?
init(profile: UIImage) {
self.profile = profile
}
mutating func updateProfile(image: UIImage) {
self.profile = image
}
}
//Address
struct Address {
var houseName: String = ""
var houseNumber: String = ""
var place: String = ""
init(houseName: String, houseNumber: String, place: String) {
self.houseName = houseName
self.houseNumber = houseNumber
self.place = place
}
func getCompleteAddress() -> String{
let addressArray = [self.houseName, self.houseNumber, self.place]
return addressArray.joined(separator: ",")
}
}
//EmployeeViewModel
class EmployeeViewModel: ObservableObject {
#Published var users : [Employee] = []
func initialize() {
self.users = [Employee(name: "ABC", id: "100", address: Address(houseName: "Beautiful Villa1", houseNumber: "17ABC", place: "USA"), userImage: UserIcon(profile: UIImage(named: "discover")!)),
Employee(name: "XYZ", id: "101", address: Address(houseName: "Beautiful Villa2", houseNumber: "18ABC", place: "UAE"), userImage: UserIcon(profile: UIImage(named: "discover")!)),
Employee(name: "QWE", id: "102", address: Address(houseName: "Beautiful Villa3", houseNumber: "19ABC", place: "UK"), userImage: UserIcon(profile: UIImage(named: "discover")!))]
}
func update() { //both below cases worked
self.users[0].address.houseName = "My Villa"
//self.users[0].updateAddress(with: Address(houseName: "My Villa", houseNumber: "123", place: "London"))
self.updateImage()
}
func updateImage() {
self.users[0].userImage.updateProfile(image: UIImage(named: "home")!)
}
}
//EmployeeView
struct EmployeeView: View {
#ObservedObject var vm = EmployeeViewModel()
var body: some View {
NavigationView {
List {
ForEach(self.vm.users) { user in
VStack {
Image(uiImage: user.userImage.profile!)
Text("\(user.name) - \(user.address.getCompleteAddress())")
}
}.listRowBackground(Color.white)
}.onAppear(perform: fetch)
.navigationBarItems(trailing:
Button("Update") {
self.vm.update()
}.foregroundColor(Color.blue)
)
.navigationBarTitle("Users", displayMode: .inline)
}.accentColor(Color.init("blackTextColor"))
}
func fetch() {
self.vm.initialize()
}
}
it's been a long time but still :
1 - mutating func is not necessary.
2 - The re-rendering won't happen if you only change the nested object and not the "observed" object it self.
3 - You can play with the getters and setters as well, to pull the wanted value to change and update it back.
Considering we have a complex object such as :
struct Content{
var listOfStuff : [Any] = ["List", 2, "Of", "Stuff"]
var isTheSkyGrey : Bool = false
var doYouLikeMyMom : Bool = false
var status : UIImage? = UIImage(systemName: "paperplane")
}
Now let's wrap/nest this object into a ContentModel for the View. If, while using the #State var contentModel : ContentModel in the View, we change change one of the properties directly by accessing the nested object(like so : model.content.status = "Tchak"), it will not trigger a re-rendering because the ContentModel itself didn't change.
Understanding this, we need to trigger a tiny useless change in the ContentModel :
struct ContentModel {
private var change : Bool = false
private var content : Content = Content() {
didSet{
// this will trigger the view to re-render
change.toggle()
}
}
//the value you want to change
var status : UIImage?{
get{
contentModel.status
}
set{
contentModel.status = newValue
}
}
}
Now what's left to do is to observe the change of the content in the view.
struct ContentPouf: View {
#State var contentModel = ContentModel()
var body: some View {
Image(uiImage: contentModel.status)
.onTapGesture {
contentModel.status = UIImage(systemName: "pencil")
}
}
}
and using an ObservableObject it would be :
class ContentObservable : ObservableObject {
#Published var content : ContentModel = ContentModel()
func handleTap(){
content.status = UIImage(systemName: "pencil")
}
}
and
#StateObject var viewModel : ContentObservable = ContentObservable()
var body: some View {
Image(uiImage :viewModel.content.status)
.onTapGesture {
viewModel.handleTap()
}
}

How to publish changes to a single object in a object array

I have the following classes
class ListItem: Identifiable {
var id: UUID
var name: String
var description: String
var isFavorite: Bool
var debugDescription: String {
return "Name: \(self.name) | Favorite?: \(self.isFavorite)"
}
public init(name: String) {
self.name = name
id = UUID()
self.description = "Some text describing why \(self.name.lowercased()) is awesome"
self.isFavorite = false
}
}
class ListItems: ObservableObject {
#Published var items: [ListItem]
let defaultAnimals = ["Ant", "Bear", "Cat", "Dog", "Elephant",
"Fish", "Giraffe", "Hyena", "Iguana", "Jackal", "Kingfisher", "Leopard", "Monkey"]
public init(animals: [String] = []) {
let animalList: [String] = animals.count > 0 ? animals : defaultAnimals
self.items = animalList.sorted {
$0.lowercased() < $1.lowercased()
}.map {
ListItem(name: $0.firstUppercased)
}
}
}
and the following image view in ContentView
struct ContentView: View {
#ObservedObject var list: ListItems = ListItems()
var body: some View {
List(list.items) {
animal in HStack {
// ...
Image(systemName: animal.isFavorite ? "heart.fill" : "heart").foregroundColor(.pink).onTapGesture {
let index = self.list.items.firstIndex { $0.id == animal.id } ?? -1
if (index >= 0) {
self.list.items[index].isFavorite = !animal.isFavorite
self.list.items = Array(self.list.items[0...self.list.items.count-1]) // <--
}
}
// ...
}
}
}
}
Everytime, the image view is tapped, I am basically reassigning the entire array like this so that the changes can be reflected in the UI
self.list.items = Array(self.list.items[0...self.list.items.count-1])
My question: How can I refactor my code to prevent reassigning the entire object array every time some object property changes?
I am fairly new to Swift & iOS development, not sure if I am missing something basic.
Declare ListItem as an struct instead of a class, this way the view will be notified when isFavorite changes. And just a little suggestion; you can use toggle to change the value of a boolean: self.list.items[index].isFavorite.toggle()

Delegate not being called in Swift

I can't figure out why my Delegate is not being called.
Here is where I define the protocol and call the delegate:
protocol CommentRatingViewControllerDelegate: class {
func didCommentOrRatePost(updatedRating: Bool, addedComment:Bool)
}
class CommentRatingViewController: UIViewController, UITextViewDelegate {
weak var delegate:CommentRatingViewControllerDelegate?
...
#IBAction func saveRatingComment(_ sender: Any) {
var updatedRating = false
var addedComment = false
rating = ratingView.rating
if rating != 0.0 {
saveRating(articleID: post.articleID, userID: post.userID)
updatedRating = true
}
if commentsTextView.text != "" {
saveComment(articleID: post.articleID, userID: post.userID, comment: commentsTextView.text!)
addedComment = true
}
self.delegate?.didCommentOrRatePost(updatedRating: updatedRating, addedComment: addedComment)
close()
}
....
And here is where conform to the delegate protocol:
extension PostDetailViewController: CommentRatingViewControllerDelegate {
func didCommentOrRatePost(updatedRating: Bool, addedComment: Bool) {
if updatedRating == true || addedComment == true {
networkingState = .searching
if updatedRating {
getRating(articleID: post.articleID)
}
if addedComment {
post.numberOfComments += post.numberOfComments
}
networkingState = .finishedSearching
tableView.reloadData()
}
}
}
When you conform to a protocol, if you want to call delegate methods, it is not enough to make your class conform to the protocol, you also need to set the delegate to self inside the class.
class CommentRatingViewController: UIViewController, UITextViewDelegate {
override func viewDidLoad() {
self.delegate = self
}
}