No exact matches in call to instance method 'append' in swift - arrays

Tried to combine or merging two model to one model
1st model = items [ InboxModel]. (My own Inbox)
2nd model = items2 [MoInboxModel] (SDK Inbox)
1st + 2nd -> combinedItems
private var items: [InboxModel] = []
private var items2: [MoInboxModel] = []
private var combinedItems: [combinedInboxModel] = []
struct InboxModel {
let id: String
let title: String
let message: String
let date: Date
}
struct MoInboxModel {
let id: String
let title: String
let message: String
let date: Date
}
struct combinedInboxModel {
let id: String
let title: String
let message: String
let date: Date
}
self?.combinedItems.append(self?.items). //No exact matches in call to instance method 'append
self?.combinedItems.append(contentsOf: self?.items2 ?? []) //No exact matches in call to instance method 'append
Why there is an error while merge it ? How to merge it correctly?

You have three unrelated types - InboxModel, MoInboxModel and combinedInboxModel (Which should be CombinedInboxModel. Even though they all have properties with the same name, they are different types.
There is no append function on an array of combinedInboxModel that accepts an array of InboxModel or MoInboxModel.
You could use map on each of your two input arrays to convert them to an array of CombinedInboxModel which you can then put into combinedItems.
Presumably you are writing this code in a closure, which is why you have a weak self. Best to deal with that first and then process your arrays.
guard let self = self else {
return
}
self.combinedItems = self.items.map { CombinedInboxModel(id:$0.id,title:$0.title,message:$0.message,date:$0.date) }
let items2 = self.items2.map { CombinedInboxModel(id:$0.id,title:$0.title,message:$0.message,date:$0.date) }
self.combinedItems.append(contentsOf:items2)
You haven't shown where items and items2 come from; Is it possible just to fetch them as instances of the same struct to start with?

The fact that you have three structs with the same properties is a bit fishy. I would consider a different design if I were you.
However, if you must go with this approach, you might want to consider starting with a protocol and getting rid of the combinedInboxModel struct.
protocol InboxModelable {
var id: String { get }
var title: String { get }
var message: String { get }
var date: Date { get }
}
Now make your two structs conform to InboxModelable.
struct InboxModel: InboxModelable {
let id: String
let title: String
let message: String
let date: Date
}
struct MoInboxModel: InboxModelable {
let id: String
let title: String
let message: String
let date: Date
}
Since both of your types conform to InboxModelable you can directly store both types in an array of type Array<InboxModelable> without having to map the elements.
class SomeClass {
private var items: [InboxModel] = []
private var items2: [MoInboxModel] = []
private var combinedItems: [InboxModelable] = []
func combineItems() {
doSomething { [weak self] in
guard let self = self else { return }
self.combinedItems.append(contentsOf: self.items)
self.combinedItems.append(contentsOf: self.items2)
}
}
}

Related

SwiftUI: How do I display multidimensional array as sections in List?

I have the following object as a var events = [[EventLog]] and I need to loop through each inner-array and display them in a section. For example, events[0] might have events[0][0].id events[0][1].id and events[0][2].id as values and events[1] may only have 1 element. This is the object that I've built for reference.
Model Object
class EventLog: Identifiable {
let id: UUID
let ipAddress: String
let date: String
let getMethod: String
let statusCode: String
let secondStatusCode: String
let versionInfo: String
init(ipAddress: String, date: String, getMethod: String, statusCode: String, secondStatusCode: String, versionInfo: String ){
self.id = UUID()
self.ipAddress = ipAddress
self.date = date
self.getMethod = getMethod
self.statusCode = statusCode
self.secondStatusCode = secondStatusCode
self.versionInfo = versionInfo
}
}
Attempts At Using
Doing it this way results in this error: Referencing initializer 'init(_:content:)' on 'ForEach' requires that '[EventLog]' conform to 'Identifiable' It should be noted that the multidimensional array is stored on a ViewModel named landingPageVM which is not referenced here for the sake of brevity. It is however an #Published property of the view model.
ForEach(landingPageVM.eventLogs) { logs in
ForEach(logs)) { log in
Text(log.id.description)
}
}
If you can change your model to a struct instead of a class, you'll have an easier time with this (and a lot of other things in SwiftUI):
struct ContentView : View {
#State var eventLogs : [[EventLog]] = []
var body: some View {
List {
ForEach(eventLogs, id: \.self) { logs in
Section(header: Text("\(logs.count)")) {
ForEach(logs) { log in
Text(log.id.description)
}
}
}
}
}
}
struct EventLog: Identifiable, Hashable {
let id: UUID
let ipAddress: String
let date: String
let getMethod: String
let statusCode: String
let secondStatusCode: String
let versionInfo: String
}
If you're stuck using a class for some reason, you can still get this behavior by doing something like:
extension EventLog : Hashable, Equatable {
func hash(into hasher: inout Hasher) {
hasher.combine(id) //etc
}
static func == (lhs: EventLog, rhs: EventLog) -> Bool {
lhs.id == rhs.id && lhs.ipAddress == rhs.ipAddress //etc
}
}
Details of the hash and == implementations may vary.

Comparing two arrays of objects in Swift

I have two arrays of objects with different sizes.
First one with old data, second one with updated data from server (included old data with new), data can be mixed. I want to get difference between these arrays.
My class
class Station {
var dateOfIssue: Date
var region: String
var locality: String
var bsName: String
var freqIn: String
var freqOut: String
var networkType: String
var provider: String
var bsUsableName: String
...
}
Arrays I want to compare (example)
var a = [Station]()
var b = [Station]()
for _ in 0...5 {
a.append(Station(someRandomStationValue...)
}
b = a
for _ in 0...7{
b.append(Station(someRandomStationValue...) //array "b" will contain all that array "a" contains and some new values
}
How to compare these arrays comparing all fields between and get a new array with differences (like in java: b.removeAll(a))?
You can make use of Set which provides in-built .subtract() and .subtracting() methods which removes the common entries inside both the Sets
struct Station: Hashable,CustomStringConvertible {
var id: Int
var region: String
var locality: String
var bsName: String
// Just to provide a pretty print statement
var description: String {
return "ID: \(id) | region: \(region) | locality: \(locality) | bsName: \(bsName)"
}
}
var stations1 = Set<Station>()
var stations2 = Set<Station>()
for currentNumber in 0...3 {
stations1.insert(Station(id: currentNumber, region: "abc", locality: "abc", bsName: "abc"))
}
for currentNumber in 0...5 {
stations2.insert(Station(id: currentNumber, region: "abc", locality: "abc", bsName: "abc"))
}
// Caluculating the difference here
print(stations2.subtracting(stations1))
As pointed out by #koropok, a good solution is using Set. The first step is to conform your type to Hashable. For classes, you'd have to implement == and hash(into:) functions, but if you use struct you don't have to do anything else other than declaring the conformance. So:
struct Station: Hashable {
var dateOfIssue: Date
var region: String
...
}
Now you should be able to add Station into a Set. Thus:
var a = Set<Station>()
for _ in 0...5 {
a.insert(Station(...))
}
var b = Set<Station>()
a.forEach { b.insert($0) }
for _ in 0...7 {
b.insert(Station(...))
}
let c = b.subtracting(a)
Set also provides a handy initializer that you can use to turn your Station array into a set:
let s = Set(arrayLiteral: [your, station, objects])
As mentioned in comments by koropok you may use subtract method:
// Added to make original code functional
// Station must conform to Hashable protocol in order to be stored in the Set
struct Station: Hashable {
let id: Int
}
var a = [Station]()
for i in 0...5 {
a.append(Station(id:i))
}
var b = [Station]()
for i in 0...7{
//array "b" will contain all that array "a" contains and some new values
b.append(Station(id:i))
}
var c = Set(b)
// c will contain 6 and 7
c.subtract(a)

Swift appending to an array with custom objects and sorting them

I am making a collection view that shows somebody their 'feed' which includes their compliments, likes, replies, and sent. I have an object named Feed which is made of an array of compliments, an array of likes, and array of replies, and an array of sent. Every object within those smaller arrays has a date_created property.
I know how to fill a collection view based off of one array sorted by dates, but how can I load a collection view with every like, reply, compliment, and sent from four arrays based on their date sent?
Do I perhaps append every one of these objects to one array? If so, how can I then go through that array and sort everything in it?
struct Feed: Decodable {
let likes: [Like]
let replies: [Reply]
let compliments: [Compliment]
let sent: [Compliment]
}
struct Like: Decodable {
let id: Int
let user: QuickUser
let created_at: String
}
struct Reply: Decodable {
let parent: Compliment
let content: String
let created_at: String
}
struct Compliment: Decodable {
let id: Int
let content: String
let sender: QuickUser
let created_at: String
let is_liked: Bool
let recipient: QuickUser
let is_public: Bool
}
func getFeed() {
UserServices.getInboxFeed { feed in
guard let feed = feed else {
self.showOkayAlert(title: "Oops!", message: "We are having trouble loading your inbox.", completion: nil)
return
}
self.feed = feed
DispatchQueue.main.async {
//load collection view here
}
}
}
A mockup of what the collection view will look like
Well, to solve this i have made a dummy protocol that contains the shared properties they all share.
in this case it was created_at and i assume this should be in date format.
however the next step was to make an extension of Feed that contains a function
that returns the latest object that confirm to the dummy protocol that i called, it LatestAction,
it basically sort each array by date and return the first item.
in our case the latest date,
after that creates a [LatestAction] sort it again the same way and return the first item which is the LatestAction.
Observe the code below.
struct QuickUser: Codable {
}
struct Feed: Decodable {
let likes: [Like]
let replies: [Reply]
let compliments: [Compliment]
let sent: [Compliment]
}
extension Feed {
func latestAction() -> LatestAction {
let latestLike = self.likes.sorted(by: { $0.created_at.compare($1.created_at) == .orderedDescending }).first!
let latestReply = self.replies.sorted(by: { $0.created_at.compare($1.created_at) == .orderedDescending }).first!
let latestComp = self.compliments.sorted(by: { $0.created_at.compare($1.created_at) == .orderedDescending }).first!
let latestActions: [LatestAction] = [latestReply, latestLike, latestComp].sorted(by: { $0.created_at.compare($1.created_at) == .orderedDescending })
return latestActions.first!
}
}
protocol LatestAction {
var created_at: Date { get }
}
struct Like: Decodable, LatestAction {
let id: Int
let user: QuickUser
let created_at: Date
}
struct Reply: Decodable, LatestAction {
let parent: Compliment
let content: String
let created_at: Date
}
struct Compliment: Decodable, LatestAction {
let id: Int
let content: String
let sender: QuickUser
let created_at: Date
let is_liked: Bool
let recipient: QuickUser
let is_public: Bool
}
// Simply used like this
var latest = feed.latestAction()
We are not done yet if you checked the latest type it would say LatestAction and only can access the one property in there which is created_at,
now the for the last part we are going to TypeCast the Object.
if let latest = feed.latestAction() as? Like {
// used as Like
} else if let latest = feed.latestAction() as? Compliment {
// used as Compliment
} else if let latest = feed.latestAction() as? Reply {
// used as reply
}

Array is nil after appending to it

I have 2 Types, first is Car with property feature and it of the second type Feature.
I have a property cars: [Car], when I append new car to it, it returns nil.
I have created a sample in the snippet below:
class Car {
let make: String
var features: Feature?
let id: String
init(make: String, features: Feature?, id: String) {
self.make = make
self.features = features
self.id = id
}
}
class Feature {
let convertible: String
init(convertible: String) {
self.convertible = convertible
}
}
var cars: [Car]?
var feature: Feature?
let featureElement = Feature(convertible: "yes")
feature = featureElement
let car = Car(make: "SomeMaked", features: feature, id: "ID")
cars?.append(car)
print(cars)
My question is: Shouldn't the array increase its count after appening to it? What am I missing?
Please note that this code is just sample, so ignore typos and coding convention.
You haven't initialized your array.
Replace your var cars: [Car]? with var cars: [Car]? = []

deleting objects if string of object matches string in a separate array

I have an nsobject class with four strings
class Post: NSObject {
var author: String!
var postID: String!
var pathToImage: String!
var userID: String!
}
I also have a separate class viewcontroller which has a function grabbing posts from firebase. I have an array called posts = [Post](), which is filled by a seperate function going through firebase and grabbing data for each photo. I also have an array called removeArray which is array of strings, which the string is the postID of certain posts. Now this is my problem, I am trying to loop through removeArray, check if the each in removeArray = to the each in posts.postID and check if they are equal. Then either I delete that each in posts.postID post, or I create a new array that is posts - posts with postID's in removeArray. Here is my code now that does not work, it just keeps posts as is.
if posts != nil {
if var array = UserDefaults.standard.object(forKey: "removeArray") as? [String] {
for each in posts {
for one in array {
if one == each.postID {
new.append(each)
}
}
}
return self.posts.count
}
}
So if you have any idea how to take a string in an array, check if that string if eqaul to a string in an array of objects.postID, and remove that object from the array if it is equal. I have tried to research a way to filter it, but so far nothing. Please give me some feedback. Thanks
My problem = http://imgur.com/a/m5CiY
var posts = [p1,p2,p3,p4,p5]
let array = ["aaa","bbb"]
var new:Array<Post> = []
for each in posts {
for one in array {
if one == each.postID {
new.append(each)
}
}
}
print("This objects should be remvoed: \(new)")
posts = Array(Set(posts).subtracting(new))
print("After removing matching objects: \(posts)")
You could use reduce(_:_:)!
class Country {
var name: String!
init(name: String) {
self.name = name
}
}
let countries = [Country(name: "Norway"), Country(name: "Sweden"), Country(name: "Denmark"), Country(name: "Finland"), Country(name: "Iceland")]
let scandinavianCountries = ["Norway", "Sweden", "Denmark"]
// Store the objects you are removing here
var nonScandinavianCountries: [Country]?
let scandinavia = countries.reduce([Country](), {
result, country in
// Assign result to a temporary variable since result is immutable
var temp = result
// This if condition works as a filter between the countries array and the result of the reduce function.
if scandinavianCountries.contains(country.name) {
temp.append(country)
} else {
if nonScandinavianCountries == nil {
// We've reached a point where we need to allocate memory for the nonScandinavianContries array. Instantiate it before we append to it!
nonScandinavianCountries = []
}
nonScandinavianCountries!.append(country)
}
return temp
})
scandinavia.count // 3
nonScandinavianCountries?.count // 2
Resouces:
https://developer.apple.com/reference/swift/array/2298686-reduce

Resources