I'm trying to populate TableView with sections. Where an artist is the Section Name and all songs of that artist are listed below. In total over 100 artists. Like so
FirstArtist
- Song 1
- Song 2
SecondArtist
- Song 1
- Song 2
OneMoreArtist...
I have an array of objects Song
struct Song {
let songName: String?
let album: String?
let artist: String?
}
var songs = [Song]()
And an array of all artists names
var artists = [String]()
As I understand, to populate tableView with sections I need a Dictionary with Artist as key and array of Songs as value, like so
var toShow = [String : [Song]]()
So, then I'm trying to loop
for artist in artists {
for song in songs {
if song.artist == artist {
toShow[artist] = [song]
// toShow[artist]?.append(song)
}
}
}
But it doesn't work.
Probably I'm going wrong way.
What is the solution in this situation?
Thanks
Update
Made a stuct
struct ArtistWithSongs {
let name: String
let songs: [Song]
init(name: String, songs: [Song]) {
self.name = name
self.songs = songs
}
}
and trying to loop
var artistWithSongs = [ArtistWithSongs]()
for artist in artists {
for song in songs {
if artist == song.artist {
artistWithSongs.append(ArtistWithSongs(name: song, songs: [song]))
}
}
}
But apparently, my variant of looping is not correct. Now I'm getting an array of objects contains duplicate keys and only one Song per Key. It looks like
[Atrist1 : [Song1], Artist1 : [Song2], Artist1 : [Song3], ...]
My question is - What is the right way to make a loop, or is it possible somehow to merge objects with an identical key inside a Dictionary to get this [Artist1 :[Song1, Song2, Song3]]?
Thanks to everyone, I found a solution here, it worked for what I was trying to achieve. Roughly like this
let toShow = artistWithSongs.reduce([ArtistWithSongs](), { partialResult, artist in
var dupe = partialResult.filter {$0.songName == group.name }.first
if let dupeArtist = dupe {
dupeArtist.songs?.append(contentsOf: artist.songs ?? [])
return partialResult
} else {
var newPartialResult = partialResult
newPartialResult.append(group)
return newPartialResult
}
})
In addition to your Song struct
struct Song {
let songName: String?
let album: String?
}
create Artist one like
struct Artist {
let name:String
let songs = [Song]()
}
Then create an array of them
var artists = [Artist]()
let s1 = Song(songName:"name1",album:"alb1")
let s2 = Song(songName:"name2",album:"alb2")
let artist1 = Artist(name:"art",songs:[s1,s2])
artists.append(artist1)
In numberOfSections return
artists.count
In numberOfRows return
artists[section].songs.count
In cellForRowAt access
let song = artists[indexPath.section].songs[indexPath.row]
If you want to transform an array of songs ([Song]) to a dictionary ([String : [Song]]), I would recommend using functional style:
self.toShow = songs.reduce([String: [Song]]()) { acc, next in
// mutable copy
var acc = acc
// since artist is optional we need to substitute it with non-optional value if it's nil
let artist = next.artist ?? "Unknown Artist"
// if dictionary already has this artist, then append the `next` song to an array
if acc[artist] != nil {
acc[artist]?.append(next)
} else {
// otherwise create new array
acc[artist] = [next]
}
return acc
}
In your table's data source provide the number of sections:
func numberOfSections(in tableView: UITableView) -> Int {
return self.toShow.keys.count
}
And then you'll face a trouble: for number of rows in section you'll need to know which artist is the section stands for. So, I suggest you to use one more structure, an array of artists so that you keep the order of sections consistent:
self.artists = Array(self.toShow.keys).sorted()
This way, you can provide your table with following numbers of sections and rows:
func numberOfSections(in tableView: UITableView) -> Int {
return self.artists.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let artist = self.artists[section] // get the artist's name
return self.toShow[artist]?.count ?? 0
}
Related
TableView consists with list of dynamic label and navigation bar consists of two button called up and down.
here i'm getting data from json and append to model. But i can not able to list in tableview. Array value should be loaded to list of label.
Here is my json appended to model:
[PO_PR.poItemsModel(Material: Optional("RMECC_MOB1"), Qty: Optional("2.000"), UoM: Optional("KG"), totalValue: Optional("1000.000"), Requistor: Optional(""), StorageLocation: Optional("3001"), MatGrp: Optional("00107"), valuationPrice: Optional("1000.000"), ItemCategory: Optional("0"), lineNo: Optional("00010"), UnitofPrice: Optional("1.000")),
PO_PR.poItemsModel(Material: Optional("RMECC_MOB1"), Qty: Optional("2.000"), UoM: Optional("KG"), totalValue: Optional("1000.000"), Requistor: Optional(""), StorageLocation: Optional("3001"), MatGrp: Optional("00107"), valuationPrice: Optional("1000.000"), ItemCategory: Optional("0"), lineNo: Optional("00020"), UnitofPrice: Optional("1.000"))]
Initially array of first index should be loaded to list of label and then when pressing down button next of array value should be loaded to list of label.
Labels in tableview will be in each cell not in a single cell.
Here is my model:
struct poItemsModel {
var Material : String?
var Qty : String?
var UoM : String?
var totalValue : String?
var Requistor : String?
var StorageLocation: String?
var MatGrp : String?
var valuationPrice : String?
var ItemCategory : String?
var lineNo : String?
var UnitofPrice : String?
init(json: JSON) {
Material = json["Material"].stringValue
Qty = json["Quantity"].stringValue
UoM = json["OrderUnit"].stringValue
totalValue = json["NetPrice"].stringValue
Requistor = json["Requistor"].stringValue
StorageLocation = json["StorageLocation"].stringValue
MatGrp = json["MatGroup"].stringValue
valuationPrice = json["NetPrice"].stringValue
ItemCategory = json["ItemCat"].stringValue
lineNo = json["Item"].stringValue
UnitofPrice = json["UnitofPrice"].stringValue
}
}
Here i'm appeding json data to model:
func GetPoItemCount() {
if orderNo != nil {
// Call API
print("orderni::\(orderNo!)")
PoVendorListApiManager.sharedInstance.getPoListWithModel(orderString: orderNo!){ (json:JSON) in
// return json from API
if let results = json["d"]["results"].array {
print("catefory::\(results)")
for category in results {
self.PoItemsArray.append(poItemsModel(json: category))
for (key, value) in category {
print("keyvalue\(key)\(value)")
self.poItemJsonArray.append(value)
self.poItemKeyArray.append(key)
let currency = newPrItemDetailModel(key: key, value: value)
self.newItemDetailsArray.append(currency)
}
}
let elm = self.PoItemsArray
let mirror = Mirror(reflecting: elm)
for child in mirror.children {
print("key: \(String(describing: child.label)), value: \(child.value)")
}
print("self.PoItemsArray array:::\(self.PoItemsArray)")
print("new model array:::\(self.newItemDetailsArray)")
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
}//extension
Here is my tableview code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// PoItems = loadPoItems()
print("count::itemArray::\(self.PoItemsArray.count)")
return PoItemsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! PoItemTableViewCell
let itemDetails = PoItemsArray[indexPath.row]
// for arrayData in
// PoItems = loadPoItems()
// prItem.getProperty(at: indexPath.row)
// cell.materials.text = PoItemsArray[0].key!
// cell.materialsValue.text = itemDetails[indexPath.row]
return cell
}
Here is my screenshot where i'm trying to achieve:
How to load to array of first index into list of dynamic labels and when pressing down button how to load next array index of values into list of dynamic labels.
Any help much appreciated pls....
This can not be done (realistically) with a table view. Table views are for long lists (a variable number) of the same item which can not change. What you have here is a fixed number (9) of totally different items, which do change.
Answer 1:
Do not use a table view for this. It's not what a table view is for.
Simply use a StackView for your layout - it's dead easy, and still scrolls etc.
Done. It will take 2 minutes.
Answer 2:
If as an exercise you want to use a table view, which would be wrong:
To do "synced / dynamic" tables is extremely difficult.
You must have a custom cell class
The button (on the main screen) must signal in some way to all of the cells to make the change in question.
This is a broad field of enquiry, you'd have to look in to the various ways to achieve this.
I am a little confused about how my Tableview is reacting. My problem is that the Tableview shows the other order than Dictionary than when I print the Dictionary. When I print the Dictionary everything seems to be fine, but when I look at my phone the tableview is showing the cell at a random order again.
First I fetch the JSON.
var Aanbiedingen_bieren = [Aanbiedingen_bier]()
var Aanbiedingen_bieren_Filter = [Aanbiedingen_bier]()
Fetch data.
func fetchData() {
Aanbiedingen_bieren_Filter.removeAll()
Aanbiedingen_bieren.removeAll()
let url_Aanbiedingen_bier = URL(string: "\(Firebase_url)")
let downloadURL = url_Aanbiedingen_bier
URLSession.shared.dataTask(with: downloadURL!) { data, urlResponse, error in
let data = data
print("downloaded")
do
{
let decoder = JSONDecoder()
let downloadedBiers = try decoder.decode(SheetJS.self, from: data!)
self.Aanbiedingen_bieren = downloadedBiers.SheetJS
for jsonData in self.Aanbiedingen_bieren {
let Soort = jsonData.Title
if self.Soort_Bier == "Alles" {
self.Aanbiedingen_bieren_Filter.append(jsonData)
}else{
if self.Soort_Bier == "Krat" {
if Soort.contains(word: "Krat") {
self.Aanbiedingen_bieren_Filter.append(jsonData)
}
}
if self.Soort_Bier == "Fles" {
if Soort.contains(word: "Fles") {
self.Aanbiedingen_bieren_Filter.append(jsonData)
}
}
}
}
self.Aanbiedingen_bieren_Filter = self.Aanbiedingen_bieren_Filter.sorted(by: {$0.Voor_prijs.compare($1.Voor_prijs, options: .numeric) == .orderedAscending})
print(self.Aanbiedingen_bieren_Filter)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}.resume()
}
The first time that the code runs the Soort_Bier = "Alles". After a button tap the Soort_Bier will change in Soort_bier = "Krat". After the data was loaded for a second time I first wanted to filter the data. I have this done by implementing an if statement that checks if the Title of the JSON has a specific word in the String and if so append it to an other array. After that, I wanted to sort the price. When the sorting is finished I wanted to print the Dictionary to see if the sorting is correct. This still seems to be the case. Then I want to reload the tableView so all of the cells will show and here is something wrong. When I want to load the dictionary for the second time, it doesn't seem to reload the tableView correctly.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Aanbiedingen_bieren_Filter.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Aanbiedingen_bieren_Cell", for: indexPath) as! Aanbiedingen_bieren_Cell
let Aanbiedingen_bier = self.Aanbiedingen_bieren_Filter[indexPath.row]
cell.Aanbiedingen_bier = Aanbiedingen_bier
return cell
}
Struct:
struct SheetJS: Codable {
var SheetJS: [Aanbiedingen_bier]
init(SheetJS: [Aanbiedingen_bier]) {
self.SheetJS = SheetJS
}
}
struct Aanbiedingen_bier: Codable {
let Logo_Image: String
let Van_prijs: String
let Voor_prijs: String
let Beschrijving: String
let Item_Image: String
let Title: String
let Bestel_Online_link: String
let Footer_item: String
let Item_1: String
let Item_2: String
init(Logo_Image: String, Item_Image: String, Van_prijs: String, Voor_prijs: String, Beschrijving: String, Title: String, Item_1: String, Item_2: String, Bestel_Online_link: String, Footer_item: String) {
self.Logo_Image = Logo_Image
self.Title = Title
self.Item_Image = Item_Image
self.Beschrijving = Beschrijving
self.Van_prijs = Van_prijs
self.Voor_prijs = Voor_prijs
self.Item_1 = Item_1
self.Item_2 = Item_2
self.Bestel_Online_link = Bestel_Online_link
self.Footer_item = Footer_item
}
}
JSON:
{
"SheetJS": [
{
"Logo_Image": "https://www.biernet.nl/images/winkel/17335-agrimarkt.gif",
"Van_prijs": "€16,99",
"Voor_prijs": "€10,49",
"Beschrijving": "Krat 24x0,30",
"Item_Image": "https://www.biernet.nl/images/soort/23026-grolsch%20krat%20normale%20flesjes%2030%20cl.png",
"Title": "Grolsch Premium Pilsener",
"Bestel_Online_link": "",
"Footer_item": "t/m zaterdag 3 augustus",
"Item_1": "€6,50 korting (38%)",
"Item_2": "€1,46 per liter"
},//Some more data
]
}
Only price printed of dictionary:
€5,39
€5,94
€6,39
€6,39
€7,64
€16,19
Result
Please let me know if you want more information or code.
It's because items in Dictionary ordered randomly.
If you want to order it, you should use 'Set' another ordered collection type.
Or see the Apple Document below
Apple Docs : The order of key-value pairs in a dictionary is stable between mutations but is otherwise unpredictable. If you need an ordered collection of key-value pairs and don’t need the fast key lookup that Dictionary provides, see the KeyValuePairs type for an alternative.
I think you haven't been using wrongly data collection. You should use array to order. Because A dictionary stores associations between keys of the same type and values of the same type in a collection with no defined ordering. Unlike array, a dictionary do not have a specified order.
Click here! to read about data collection in swift
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
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!
i want to make like a group array
struct songST {
var singerid:[Int]
var songname:[String]
var songembeded:[String]
}
**fist item;**
songST(singerid: "1", songname: ["Its my life"], songembeded: ["url1"])
**seconditem=**
songST(singerid: "1", songname: ["Always"], songembeded: ["url2"])
**i want to make join like this**
songST(singerid: "1", songname: ["Its my life","Always"], songembeded: ["url1","url2"])
if there was same singerid than will join
how can i do that? Please Help me.
I think you can improve how data is modeled.
A song consists of the following fields:
singer id
song name
song url
So it would make sense to create a model with exactly those fields, as opposed to creating an array for each of them:
struct Song {
let singerId: Int
let songName: String
let songEmbedded: String
}
Next, you need a data container for all your songs, which must be grouped by singer. The easiest way is to use a dictionary, whose key is the singer id and the value is an array of Song.
Rather than manually creating and handling the dictionary, it's better if a proper data container is created:
struct Singers {
var singers: [Int : [Song]] = [:]
mutating func addSong(song: Song) {
if self.singers[song.singerId] == nil {
self.singers[song.singerId] = []
}
self.singers[song.singerId]?.append(song)
}
func getSongs(singerId: Int) -> [Song] {
if self.singers[singerId] == nil {
return []
}
return self.singers[singerId]!
}
}
The Singers struct contains a dictionary property, and two method:
addSong adds a song, assigning it to the proper array identified by the key (singer id)
getSongs returns an array of songs for a singer id
Some sample code taken from a storyboard:
let s1 = Song(singerId: 1, songName: "Its my life", songEmbedded: "url1")
let s2 = Song(singerId: 1, songName: "Always", songEmbedded: "url2")
let s3 = Song(singerId: 2, songName: "Another", songEmbedded: "url3")
var singers = Singers()
singers.addSong(s1)
singers.addSong(s2)
singers.addSong(s3)
singers.getSongs(1) // Returns an array of 2 items
singers.getSongs(2) // Returns an array of 1 item
singers.getSongs(3) // Returns an empty array (no song for singer 3)
Easy.
You asked - "if there was same singerid than will join".
Make a function that uses that if statement:
func combineFirstSongST(firstSongST: songST, withSecondSongST secondSongST: songST) -> songST?
{
if firstSongST.singerid != secondSongST.singerid
{
return nil
}
else
{
return songST(singerid: firstSongST.singerid, songname: firstSongST.songName + secondSongST.songName, songembeded: firstSongST.songembeded + secondSongST.songembeded) //single statement
}
}
You should use camel case.