IOS Swift reading data from a dictionary of [String: [AnotherKindOfDictionary] ] ( ) - ios

I would like to read data from a dictionary that contains dictionaries of images (or any sort of object really). Each dictionary has a key (the String).
For a somewhat visual understanding this is what I am trying to achieve:
userIdOne -> [image1, image2, image3, image4]
userIdTwo -> [image1, image2, image3]
userIdThree -> [image1, image2, image3, image4, image5]
userIdFour -> [image1, image2]
NOTE: these images are not the same image despite having the same "title". They just belong to each individual user. The userId is the [String:... and the dictionary of images is the [AnotherKindOfDictionary] I mentioned in the title of this question. I want each userId and their images in each cell. So in total, this would show 4 cells, BUT when tapped, their images would show in sequential order.
The problem is that I want to put this data in a UITableView or UICollectionView. I've worked with both before so whichever works.
Something similar to how snapchat works. Whenever a cell is tapped, the images from that user are shown sequentially.
I've been able to load the data into the dictionary with each userID being the key but I am having trouble using the data in a collectionView(my current choice, although I can use a tableView)
Here is my code:
var stories = [String : [StoryMedia]]()
// StoryMedia is a struct containing info
struct StoryMedia {
var storyMediaId: String?
var creatorId: String?
var datePosted: String?
var imageUrl: String?
init(storyMediaKey: String, dict: Dictionary<String, AnyObject>) {
storyMediaId = storyMediaKey
creatorId = dict["creatorId"] as? String
datePosted = dict["dateposted"] as? String
imageUrl = dict["imageUrl"] as? String
}
}
... Now in the actual viewController class UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return stories.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let storyCell: StoryCell!
storyCell = collectionView.dequeueReusableCell(withReuseIdentifier: storyReuseIdentifier, for: indexPath) as! StoryCell
// What should I do here?
return storyCell
}
The problem lies with trying to setup each cell. I cannot pull each dictionary value by its key and use it for each cell.
I've tried using:
// Failed attempt 1)
let story = stories[indexPath.row]
// but I get an ambiguous reference to member 'subscript' error
// Failed attempt 2)
for story in stories {
let creatorId = story.key
let sequenceOfStoryItems = story.value
for singleStoryItem in sequenceOfStoryItems {
// do something...
}
}
// but looping through an array for a collection view cell
// does nothing to display the data and if I were to guess,
// would be detrimental to memory if
// I had a lot of "friends" or users in my "timeline"

A dictionary isn't ordered, so it's awkward to use that for the cellForItem function (and why you can't use that subscript). You might be able to use the dictionary's values (i.e. ignore the keys), but that could be different b/n runs. What I mean is, you can use the subscript on the stories.values array (don't remember the exact call for "values", but it's close...allValues?...not sure, don't have a way to double check right now) instead of the stories dictionary.

Does var stories = [String : [StoryMedia]]() need to be a Dictionary?
Dictionaries aren't ordered, so can't index them like you are asking. Can you make it an array of tuples? var stories = [(username:String, media:StoryMedia)]() Then you can add whatever value you were planning to store in the original key into the username: field on the tuple. You could make another struct that has username and media properties if you prefer over the tuple.
It should be trivial to pull individual username or media structs out of the array with a simple stories.filter{} call.

Related

How can I include URLs in a Table Struct (Array) and call them in didSelectRowAt?

I am working in xCode 10.2 with swift. I have created global variables in my first view controller for 6 tableviews. In storyboard, I have 6 tableViewControllers. In a separate swift file, I have created a table struct to hold an array and display the data in each corresponding cell. In each view controller in didSelectRowAt connects the next table view. My problem is when I get to the last table view. I need to associate website URLs to the array on the fifth table. I keep getting an error stating cannot convert string to URL. Please Help!
var fifthArray = [
FifthTableStruct(FifthTitle: ["Energy Guide", "https://www.google.com", "Warranty Page", "Use & Care Guide", "Specification Sheet", "FIT System", "Installation Instructions"]),
FifthTableStruct(FifthTitle: ["Energy Guide", "Warranty Page", "Use & Care Guide", "Specification Sheet", "FIT System", "Installation Instructions"])
]
var sixthArray = [
SixthTableStruct(SixthTitle: ["https://www.whirlpool.com/content/dam/global/documents/201708/EnergyGuide-W11037203-RevA.pdf", "https://www.whirlpool.com/content/dam/global/documents/201708/WarrantyPage-W11037201-W.pdf", "https://www.whirlpool.com/content/dam/global/documents/201708/UseandCareGuide-W11037201-RevA.pdf", "https://www.whirlpool.com/content/dam/global/documents/201711/WL170160A_p2.pdf", "https://www.whirlpool.com/content/dam/global/documents/201901/wash-performance-guarantee-en.pdf", "https://www.whirlpool.com/content/dam/global/documents/201711/InstallationInstructions-W10682737-RevA.pdf"])
]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let urlString = self.sixthArray[indexPath.row]
if let url = URL(fileURLWithPath: urlString)
{
UIApplication.shared.openURL(url)
}
}
I have the code for the tableStruct in an Array file separate from the viewController.
import Foundation
import UIKit
struct SecondTableStruct {
var SecondTitle = [String]()
}
struct ThirdTableStruct {
var ThirdTitle = [String]()
}
struct FourthTableStruct {
var FourthTitle = [String]()
}
struct FifthTableStruct {
var FifthTitle = [String]()
}
struct SixthTableStruct {
var SixthTitle = [String]()
}
sixthArray is an array of SixthTableStructs, a SixthTableStruct has a single field, SixthTitle, whose type is an array of String.
So to get to a single string stored within sixthArray you need to:
Index into sixthArray to obtain a single value of type SixthTable, let's call this intermediate1
Select the SixthTitle field of intermediate1 To obtain a value of type array of String, let's call this intermediate2
Index into intermediate2 to obtain a single String value
In code:
let intermediate1 = sixthArray[someIndex]
let intermediate2 = intermediate1.SixthTitle
let urlString = intermediate2[someOtherIndex]
We can't tell you what the two index values you need are, one is presumably indexPath.row. (You can of course write the above three lines as one without the intermediates if you wish.)
A couple of suggestions, first you appear to have page titles and associated URLs, which form a closely connected pair of data values, broken up and stored in separate arrays requiring you to carefully manage the order of items in those arrays and losing the close association between the items. Consider a single array of some struct, say PageDetails, with appropriate properties, say title and URL, to keep these together.
Second, arrays can hold URLs, not just Strings...
HTH
In your didSelectRowAt do following, Currently you are directly accessing struct but not its array having name SixthTitle
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let urlString = self.sixthArray.SixthTitle[indexPath.row]
if let url = URL(fileURLWithPath: urlString)
{
UIApplication.shared.openURL(url)
}
}

Number of item count into UICollectionView

I am trying to display some data in the collection view. Data will be coming from the server. I am able to get and show the data in collection view when I gave the number of items as static like return 20. Whenever I tried to display data from a server like return array.count, that time I am not able to display the data. I just simply got the array of data from the server and added that array to globally declared array, in the number of items section I have given return globallydeclaredarrayobj.count. Can anyone helps me, would be great? Thank in advance.
//Globally declared variable
var pro = [[String:Any]]()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return pro.count
// return 20
}
//Data from server
var productsdetails = [[String:Any]]()
productsdetails = userdata.value(forKey: "products") as! [[String:Any]]
self.pro = productsdetails
print("result\(self.pro)")
self.collectionview.reloadData()
Try to print the count before you returning the count in
print(Pro.count)
retrurn Pro.count

Swift: Copying oldArray[][] to newArray[][] causes error (Type 'Any' has no subscript member)

Using Swift for my collectionView app. I'm reading a plist file to display the info in the arrays.
This is the code to read the plist which creates an array called sections[section #][item #] which lets me access items inside the arrays of the root.
var Items = [Any]()
let url = Bundle.main.url(forResource:"items", withExtension: "plist")!
do {
let data = try Data(contentsOf:url)
let sections = try PropertyListSerialization.propertyList(from: data, format: nil) as! [[Any]]
for (index, section) in sections.enumerated() {
//print("section ", index)
for item in section {
//print(item)
}
}
print("specific item: - \(sections[1][0])") // (Section[] Item[])
print("section count: - \(sections.count)")
Items = sections
} catch {
print("This error must never occur", error)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch checkGroupChoice {
case 0:
print("From: Items")
print("specific item 2: - \(Items[0][1])")
return Items.count
I created var Items = [Any]() to transfer everything from sections[][] to the array Items so I can use it as a global array for my collectionsView but I get an error.
Type 'Any' has no subscript members
on the line print("specific item 2: - \(Items[0][1])")
How do I successfully transfer sections[][] to Items? I'm sure I'm not creating the array correctly. Thank you
Items is a ONE dimensional array.. you are trying to index it as a 2D array.. Change it to:
var Items = [[Any]]()
Now you can assign to it and append to it and index it as a 2D array. Each dimension requires a matching set of square brackets..
Example:
1D array: [Any]()
2D array: [[Any]]()
3D array: [[[Any]]]()

Mapping between array of enums and a dictionary of enums

Here's what I'm trying to do:
Provide a tableView that allows the user to select a formula name (an enum) that will be set as their default
In this view I'll place a checkmark next to the formula that is the current default
Their chosen default formula is what will determine which formula is used to calculate a 1 rep maximum amount (the theoretical amount the user could lift based on actual lifts of lesser weight)
After calculating the value, the user will be able to save a record of the lifts they did, the formula used to calculate the maximum, and the calculated maximum weight.
That record will be saved in Core Data with one of the entities being Lift which will simply be the formula name
I've created a class that I want to handle the work of providing the current default formula to what ever part of the app needs it as well as set the default when a new one is selected.
Here is my enum of the formula names:
enum CalculationFormula: Int {
case Epley
case Brzychi
case Baechle
case Lander
case Lombardi
case MayhewEtAl
case OConnerEtAl
}
and with help from the SO community, I created this class to handle the management of userDefaults:
class UserDefaultsManager: NSUserDefaults {
let formulas = [CalculationFormula.Baechle, CalculationFormula.Brzychi, CalculationFormula.Epley, CalculationFormula.Lander, CalculationFormula.Lombardi, CalculationFormula.MayhewEtAl, CalculationFormula.OConnerEtAl]
let formulaNameDictionary: [CalculationFormula : String] =
[.Epley : "Epley", .Brzychi: "Brzychi", .Baechle: "Baechle", .Lander: "Lander", .Lombardi: "Lombardi", .MayhewEtAl: "Mayhew Et.Al.", .OConnerEtAl: "O'Conner Et.Al"]
let userDefaults = NSUserDefaults.standardUserDefaults()
func getPreferredFormula() -> CalculationFormula? {
guard NSUserDefaults.standardUserDefaults().dictionaryRepresentation().keys.contains("selectedFormula") else {
print("No value found")
return nil
}
guard let preferredFormula = CalculationFormula(rawValue: NSUserDefaults.standardUserDefaults().integerForKey("selectedFormula")) else {
print("Wrong value found")
return nil
}
return preferredFormula
}
func setPreferredFormula(formula: CalculationFormula) {
NSUserDefaults.standardUserDefaults().setInteger(formula.rawValue, forKey: "selectedFormula")
}
You can see I have an array of the enums in the order I want them displayed in the tableView and a dictionary of the enums so I can get each enum's string representation to display in each cell of the tableView. Here's how I populate the cell text label which works:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("formulasCell", forIndexPath: indexPath)
let currentFormula = formulas[indexPath.row].formulaName
cell.textLabel?.text = currentFormula
return cell
}
and here's where I'm setting the checkmark anytime a cell in the tableView is selected
func refresh() {
let preferredFormula = defaults.getPreferredFormula()
for index in 0 ... formulas.count {
let indexPath = NSIndexPath(forItem: index, inSection: 0)
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
cell.accessoryType = preferredFormula == index ? .Checkmark : .None
}
}
}
As I mentioned at the beginning, I need to do many different things with this enum but to keep this question focused on one thing, I'll stay with this checkmark example which now doesn't work after creating my UserDefaultsManager class
The problem is obvious - preferredFormula is now an enum and I can't compare that to the index value which is an Int - but the solution is not. I could get the raw value of the enum but the rawValues aren't guaranteed to be in alignment with the cell indexPaths. Some ideas I've had are:
I could probably change the order of the enum cases so their raw values match the order I've put them in my formulas array, but that seems silly and unreliable
I could use the array index values but that seems equally silly and unreliable
If I just use the array, I don't have the string representations of the cases to display in the cells
It seems that using the array and dictionary together is a viable but the best I could come up with is maybe creating another dictionary that maps the enums to Ints but that would have the same issues I just listed.
Any guidance someone could provide would be greatly appreciated.
You seem to have made things a little more complicated than they need to be.
Firstly, you can use a String raw value for your enum and avoid the associated dictionary:
enum CalculationFormula: String {
case Epley = "Epley"
case Brzychi = "Brzychi"
case Baechle = "Baechle"
case Lander = "Lander"
case Lombardi = "Lombardi"
case MayhewEtAl = "Mayhew Et.Al."
case OConnerEtAl = "O'Conner Et.Al"
}
Second, Your UserDefaultsManager doesn't need to subclass NSUserDefaults, it is simply some utility functions. Also, you are doing a lot of checking in getPreferredFormula that you don't need to. I would suggest re-writing that class to use a computed property like so:
class UserDefaultsManager {
static let sharedInstance = UserDefaultsManager()
private var defaultsFormula: CalculationFormula?
var preferredFormula: CalculationFormula? {
get {
if self.defaultsFormula == nil {
let defaults = NSUserDefaults.standardUserDefaults()
if let defaultValue = defaults.objectForKey("selectedFormula") as? String {
self.defaultsFormula = CalculationFormula(rawValue: defaultValue)
}
}
return self.defaultsFormula
}
set {
self.defaultsFormula = newValue
let defaults = NSUserDefaults.standardUserDefaults()
if (self.defaultsFormula == nil) {
defaults.removeObjectForKey("selectedFormula")
} else {
defaults.setObject(self.defaultsFormula!.rawValue, forKey: "selectedFormula")
}
}
}
}
I have made the class a singleton; although this may have an impact on testability it simplifies issues that could arise if the default is changed in multiple places.
The appropriate place to set/clear the check mark is in cellForRowAtIndexPath so that cell reuse is handled appropriately. This code assumes that formulas is an array of CalculationFormula:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("formulasCell", forIndexPath: indexPath)
let currentFormula = formulas[indexPath.row]
cell.textLabel?.text = currentFormula.rawValue
let preferredFormula = UserDefaultsManager.sharedInstance.preferredFormula
cell.accessoryType = currentForumula == preferredFormula ? .Checkmark : .None
return cell
}

Making unordered items in an array to always in same order

My app has a VC with tableview which contains 10 cells(questions). User selects whatever they apply to them and press "next" button. Then, it goes to next VC has a table view and initialize corresponding section to the questions.
For example, if I select "Question1, Question 2" out of 10 questions, then it should make two sections which have titles of "Question1" and "Question 2".
Right now I add a question selected in an array like following:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
result.append(allCriteria[indexPath.row])
}
Then, when "next" button is pressed, it passes result array to next VC. Based on the array "result" is passed, it initializes sections in order.
The problem is that it changes the order of section if I press the questions in random order. And this is expected.
Would there be anyway solve this?
In short, even if a user selects questions in order of "Question2, Question1", I would like sections to be made in order of "Question1, Question2"
result array is type of [Question]! and Question class is like below:
class Question {
var documents: [Document] = []
var title: String
var description: String
init(dict: [String:AnyObject] ) {
// parse dict to generate a bunch of documents, add them to self.documents
self.title = dict["Title"] as! String
self.description = dict["Description"] as! String
let documentsDicts = dict["Documents"] as! [Dictionary<String,AnyObject>]
for dictionary in documentsDicts {
let document = Document(dict: dictionary)
self.documents.append(document)
}
}
}
Instead of adding the Question to the result array when user selects a cell, you can just add the indexPath of that cell to an array. When user clicks next, you just sort that indexPath array by ascending order, then based on that, you just generate the result array and pass it to the next view controller, something like:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
indexPathArray.append(indexPath)
}
func next() {
let sorted = indexPathArray.sort(<)
let result = sorted.map {
(var index) -> Question in
return allCriteria[index]
}
// ........
}
Reading from here, a solution can be to sort your array alphabetically by question's title and do something like this:
result.sort { $0.title < $1.title }
In Swift 3
self.resultArry.sort(by: { $0.title < $1.title })

Resources