How to change array based on tapped cell - ios

In HomeViewController, I have 5 static collection view cells:
- Alkalinity, Calcium, Magnesium, Nitrate, Phosphate
When a user taps a cell, it navigates to Details View Controller, into a variable called var dataReceived: Parameter?
Parameter model is used in HomeViewController to define the product name.
What I need to do is:
- If alkalinity cell is tapped, VC B should have an array of Alkalinity Product ex: alk 1, alk2, alk3
If a user taps calcium the array should be cal1 cal2 cal 3 and so on...
I tried using an enum and a switch statement.
MODEL
class Parameter {
var name: String
init(name: String) {
self.name = name
}
}
class Product {
var element: Element?
var name: [String]
init(element: Element?, name: [String]) {
self.element = element
self.name = name
}
}
enum Element {
case alkalinity
case calcium
case magnesium
}
VCB START HERE
class DetailViewController: UITableViewController {
var dataReceived : Parameter? // use this to get arrays the change
var alkalinityProducts = ["Al1", "a2", "a3", "a4", "a5", "a6"]
override func viewDidLoad() {
super.viewDidLoad()
self.title = dataReceived?.name
}
}
I need to find out how to update this, am I wrong with how I Modeled the data?

Related

How to display all items from a Set

My app consists on an image of various foods, in which the user taps the image and adds this food into a Set<Food>.
I want to show all items from this Set inside the class called Favorites, as a: Text("You like: \(favorites.comidas)") but I can't manage to make it work
class Favorites: ObservableObject {
var foods: Set<Food>
}
class Favorites: ObservableObject {
var foods: Set<Food>
init() {
// load the saved data
foods = []
}
func contains(_ food: Food) -> Bool {
foods.contains(food)
}
func add(_ food: Food) {
objectWillChange.send()
foods.insert(food)
save()
}
func delete(_ food: Food) {
objectWillChange.send()
foods.remove(food)
save()
}
}
struct Food: Identifiable, Hashable {
var id: Int
let name: String
let foodImage: [String]
// Equatable
static func == (lhs: Food, rhs: Food) -> Bool {
lhs.id == rhs.id
}
}
#EnvironmentObject var favorites: Favorites
let food: Food
var body: Some View {
Image(food.foodImage[0])
.onTapGesture {
if favorites.contains(food) {
favorites.delete(food)
} else {
favorites.add(food)
}
}
}
You haven't shown your Food structure, but I will assume it has a property, name.
ListFormatter is your friend with a task like this. Its string(from:[]) function takes an array and returns it in a nicely formatted list. You can use map to get an array of name strings from your set.
For the input array ["pizza","tacos","chocolate"] it will give "pizza, tacos and chocolate"
var favoriteList: String {
let formatter = ListFormatter()
let favorites = formatter.string(from:self.favorites.foods.map{$0.name})
return favourites ?? ""
}
Then you can use this function in a Text view:
Text("You like \(self.favoriteList)")
Note that a Set is unordered, so it might be nice to sort the array so that you get a consistent, alphabetical order:
var favoriteList: String {
let formatter = ListFormatter()
let favorites = formatter.string(from:self.favorites.foods.map{$0.name}.sorted())
return favourites ?? ""
}
Thanks to a tip from Leo Dabus in the comments, in Xcode 13 and later you can just use .formatted -
var favoriteList: String {
return self.favorites.foods.map{$0.name}.sorted().formatted() ?? ""
}

How do I implement abstract methods with different parameters/return types

I have parent class called "Item", and child class called "YTListItem". I want to have a functions called instantiate() which will be implemented in each child class, and return a view controller. The type of the view controller will be different depending on which child class is calling the instantiate() method. My issue is that swift does not seem to recognize me overriding the function if it has different parameters or return types.
The error occurs when I override the function, I get the error "Method does not override any method from its superclass".
class Item {
var name: String
var description: String
init(name: String = "Test text", description: String = "Test description of the item") {
self.name = name
self.description = description
}
func instantiate() {}
}
class YTListItem: Item {
var address: String
init(name: String = "Test text", description: String = "Test text", address: String = "SivTGfXxYz0") {
self.address = address
super.init(name: name, description: description)
}
override func instantiate(storyboard: UIStoryboard) -> YTDescViewController? {
let vc = storyboard.instantiateViewController(identifier: "desc") as? YTDescViewController
return vc
}
}
Is there a way to do this? I've seen mentions of protocols when searching how to make it work but I'm new to swift so I'm hoping to be able to get through it using methods I've already learned.
First of all I don't think you should be instantiating a ViewController from you model class. Instead you should be injecting your model to the ViewController. But for your particular scenario you can just return a UIViewController from instantiate function rather than it's subclass (YTDescViewController). And I think you should use protocols for your scenario something like this:
protocol Item {
var name: String {get set}
var description: String {get set}
var storyboard : UIStoryboard {get set}
func instatntiate() -> UIViewController
}
extension Item {
var viewControllerID : String {
return "desc"
}
}
struct YTListItem: Item {
var name: String
var description: String
var storyboard: UIStoryboard
func instatntiate() -> UIViewController {
return storyboard.instantiateViewController(identifier: viewControllerID)
}
}
You can also use associatedType to customize the return type of instantiate (or the parameters) in Item protocol. But in that case you would need to implement type erasures to hold generic reference to class/struct objects that implement Item protocol.

Swift segmented contorl show/hide cells in uicollectionview depending on firebase logic

I'm having a hard time trying to display/hide my collectionview cells depending on the items child nodes in FB realtime database. The problem consists of three parts: FB database, a collectionview and a segmented control. My goal is to show different cells in the collectionview depending on whether a the item has a child with a certain string value.
My database looks like this:
Items
category1
item1
name: item1
imageUrl: item1Url
logic
one
three
item2
name: item1
imageUrl: item1Url
logic
two
three
category2
item1
name: item1
imageUrl: item1Url
logic
two
four
item2
name: item1
imageUrl: item1Url
logic
one
two
I also have a custom Product class to display my items in their cells:
class Product {
var category: String?
var name: String?
var imageUrl: String?
init(rawData: [String: AnyObject]) {
name = rawData["name"] as? String
imageUrl = rawData["imageUrl"] as? String
category = rawData["category"] as? String
}
}
I load my items from firebase database with this funciton:
func loadCategoryName() {
ref = Database.database().reference().child("Items").child(selectedCategoryFromPreviousVC)
ref.observeSingleEvent(of: .value) { (snapshot) in
if let data = snapshot.value as? [String: AnyObject] {
self.itemArray = []
let rawValues = Array(data.values)
for item in rawValues {
let product = Product(rawData: item as! [String: AnyObject])
product.category = self.selectedCategoryFromPreviousVC
self.itemArray.append(product)
}
// Sort item array by rating; if rating is same, sort by name
self.itemArray.sort { (s1, s2) -> Bool in
if s1.rating == s2.rating {
return s1.name! < s2.name!
} else {
return s1.rating > s2.rating
}
}
self.collectionView?.reloadData()
}
}
}
My itemArray now contains all my items as custom Products and I can display them in their cell.
My segmented control:
func someFunc() {
let segmentController = UISegmentedControl(items: ["one", "two", "three", "four"])
segmentController.selectedSegmentIndex = 0
self.navigationItem.titleView = segmentController
segmentController.addTarget(self, action: #selector(handleSegment), for: .valueChanged)
}
#objc fileprivate func handleSegment() {
print(segmentController.selectedSegmentIndex)
}
with the handleSegment function I'm able to print out which segment has been selected. But this is where the problems occur. I've tried creating new arrays to split the items so that items are in an array depending on their "logic" child nodes. However I'm not able to make the arrays of type Product, so that I can use them to repopulate the collectionview. Also I'm not really sure what the best way of storing the logic part in my database would be.
extend your Product class:
class Product {
...
let logic: [String]?
init(...) {
...
logic = rawData["logic"] as? [String]
}
}
In your CollectionViewDataSource add some variables to store current state
var products: [Products]
var filteredProducts: [Product]
var currentFilter: String? {
didSet {
guard let currentFilter = currentFilter else {
filteredProducts = products
return
}
filteredProducts = products.filter { product in
return product.logic.contains(currentFilter)
}
}
}
extend your handleSegment func:
#objc fileprivate func handleSegment() {
print(segmentController.selectedSegmentIndex)
currentFilter = segmentController.titleForSegment(at: segmentController.selectedSegmentIndex)
collectionView.reloadData()
}
In your collectionView datasource, use filteredProducts to build the cells.

Displaying image selected between collection view controllers

I have structs as follows in a First Collection View Controller
struct Area{
var name = String()
var image = String()
}
var area = [Area]()
and in Second Collection View Controller
struct AreaSelected {
var imageSelected = String()
}
var areaSelected = [AreaSelected]()
I want to display image selected from First Collection View Controller in the Second Collection View Controller.
So I did this for navigating them to Second Collection View Controller at didSelectItemAt indexPath
let indexPaths = self.areaCV!.indexPathsForSelectedItems!
var indexPath = indexPaths[0] as IndexPath
let detailViewController = self.storyboard?.instantiateViewController(withIdentifier: "SVC") as? SecondViewController
detailViewController?.areaSelected = [self.area[(indexPath as NSIndexPath).item]]
Them I am getting following compiler error
Cannot convert value of type 'Area' to expected element type
'AreaSelected'
How do I get rid of this error?
Basically
let selectedArea = self.area[(indexPath as NSIndexPath).item]
detailViewController?.areaSelected = [AreaSelected(imageSelected: selectedArea.name)]
PS: You are using different (incompatible) types in different view controllers so definitely you can't assign it directly to each other.
PSS: Much easier, cleaner and better to setup segue on CellSelected between ViewControllers, and assign areaSelected in func prepare(for segue:UIStoryboardSegue, sender: Any?)
The error message is pretty clear, your first struct is type: FirstCollectionViewController.Area and the second has a type SecondCollectionViewController.AreaSelected. The two are as different as Int and String, you can't assign one to another as you can't do let number: Int = "Of course no". However you can define a common type:
protocol AreaDescription {
var image: String { get }
}
class FirstVC: ... {
struct Area: AreaDescription {
var name: String
var image: String
}
}
class SecondVC: ... {
struct AreaSelected: AreaDescription {
var name: String
var image: String
}
}
And set your property as:
var areaSelected = [AreaDescription]()

insertRowAtIndexes watchKit Swift

I have a struct
struct Question {
var title: [String]
var additionalInfo: String?
var answers: [String]
}
and a variable to which i add the data
var questions = [
Question(title: ["What is the color of?", "additional color information"], additionalInfo: nil, answers: [
"Blue",
"Gray"
])
]
This data loads up in a tableView on AppleWatch. Two row types are separated - TitleRowType (for titles array) and AnswersRowType (for answers array).
When i insert values into struct's array - i want to the rows in the tableView be inserted with animation.
I know that there's a insertRowAtIndexes function, but i cannot wrap my head around it. The example provided in Apple's documentation doesn't work for me. That's what i came up with:
let indexSet = NSIndexSet(index: Int) // Int is passed via the function
tableView.insertRowsAtIndexes(indexSet, withRowType: "TitleRowType")
but when i run it - the table doesn't update.
Looking forward to your advices.
You have to do 3 steps:
Add the new data to your array
Insert a row into the table
Populate the row with the new data
Here is a simple example:
class InterfaceController: WKInterfaceController {
#IBOutlet var table: WKInterfaceTable!
var items = ["row1", "row2", "row3"]
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
loadTable()
}
func loadTable() {
table.setNumberOfRows(items.count, withRowType: "tableRow")
var rowIndex = 0
for item in items {
if let row = table.rowControllerAtIndex(rowIndex) as? TableRowController {
row.label.setText(item)
}
rowIndex++
}
}
#IBAction func insertRow() {
items.append("row4")
let newIndex = items.count
table.insertRowsAtIndexes(NSIndexSet(index: newIndex), withRowType: "tableRow")
if let row = table.rowControllerAtIndex(newIndex) as? TableRowController {
row.label.setText(items[newIndex])
}
}
}
TableRowController is a NSObject subclass that has one WKInterfaceLabel outlet to display the number of the row.
I used a button to trigger insertRow()

Resources