Why doesn't it show me the items added in the array? (I want to keep this array layout).
Could someone tell me how should I complete the array to register the elements correctly?
It always returns zero.
Sample Code:
import UIKit
class FilterCategory: Equatable{
static func == (lhs: FilterCategory, rhs: FilterCategory) -> Bool {
return lhs.id == rhs.id
}
var id : Int = 0
var name: String = ""
var image: UIImage = UIImage()
init(id: Int, name: String, image: UIImage) {
self.id = id
self.name = name
self.image = image
}
}
class demo : UIViewController {
var images = [FilterCategory]()
var thumbnailImagesList = [Int: [UIImage?]]()
override func viewDidLoad() {
super.viewDidLoad()
images.append(FilterCategory.init(id: 1, name: "1.1", image: UIImage()))
images.append(FilterCategory.init(id: 1, name: "1.2", image: UIImage()))
images.append(FilterCategory.init(id: 2, name: "2.1", image: UIImage()))
images.append(FilterCategory.init(id: 2, name: "2.2", image: UIImage()))
var index = 0
for img in images {
self.thumbnailImagesList[img.id]?[index] = UIImage()
index = index+1
}
print(thumbnailImagesList.count)
}
}
The dictionary is empty from start but you are treating it like it contains values, use the below code instead that will either add the image object to a new or existing array depending on if the key (img.id) exists or not in the dictionary
for img in images {
self.thumbnailImagesList[img.id, default:[]].append(UIImage())
}
A question is why you are creating new image objects everywhere in your code, maybe you meant
for img in images {
self.thumbnailImagesList[img.id, default:[]].append(img.image)
}
Related
This is my data structure
struct SPPWorkout: Codable {
static let setKey = "Sets"
static let exerciseID = "id"
var id: Double? = 0.0
var duration: String?
var calories: Int?
var date: String?
var exercises: [ExerciseSet]
[...]
}
struct ExerciseSet: Codable {
let id: String
let name: String
var reps: Int
var weight: Double
[...]
}
extension ExerciseSet: Equatable {
static func ==(lhs: ExerciseSet, rhs: ExerciseSet) -> Bool {
return lhs.id == rhs.id
}
}
and in a SwiftUI view I'm trying to modify an ExerciseSet from user input
#State private var sppWorkout: SPPWorkout!
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
guard let editingIndex = editingIndex else { return }
sppWorkout.exercises[editingIndex].reps = Int(reps) ?? 0
sppWorkout.exercises[editingIndex].weight = Double(weight) ?? 0.0
self.editingIndex = nil
})
}
The issue is here
sppWorkout.exercises[editingIndex].reps = Int(reps) ?? 0
sppWorkout.exercises[editingIndex].weight = Double(weight) ??
and I've tried in all ways to update it, both from the view and with a func in SPPWorkout. I've also tried to replace the object at index
var newSet = ExerciseSet(id: [...], newValues)
self.exercises[editingIndex] = newSet
but in no way it wants to update. I'm sure that somewhere it creates a copy that it edits but I have no idea why and how to set the new values.
Edit: if I try to delete something, it's fine
sppWorkout.exercises.removeAll(where: { $0 == sppWorkout.exercises[index]})
Edit 2:
It passes the guard statement and it does not change the values in the array.
Edit 3:
At the suggestion below from Jared, I've copied the existing array into a new one, set the new values then tried to assign the new one over to the original one but still, it does not overwrite.
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
print(sppWorkout.exercises)
guard let editingIndex = editingIndex else { return }
var copyOfTheArray = sppWorkout.exercises
copyOfTheArray[editingIndex].reps = Int(reps) ?? 0
copyOfTheArray[editingIndex].weight = Double(weight) ?? 0.0
//Copy of the array is updated correctly, it has the new values
sppWorkout.exercises = copyOfTheArray
//Original array doesn't get overwritten. It still has old values
self.editingIndex = nil
Edit 4: I've managed to make progress by extracting the model into a view model and updating the values there. Now the values get updated in sppWorkout, but even though I call objectWillChange.send(), the UI Update doesn't trigger.
full code:
class WorkoutDetailsViewModel: ObservableObject {
var workoutID: String!
#Published var sppWorkout: SPPWorkout!
func setupData(with workoutID: String) {
sppWorkout = FileIOManager.readWorkout(with: workoutID)
}
func update(_ index: Int, newReps: Int, newWeight: Double) {
let oldOne = sppWorkout.exercises[index]
let update = ExerciseSet(id: oldOne.id, name: oldOne.name, reps: newReps, weight: newWeight)
sppWorkout.exercises[index] = update
self.objectWillChange.send()
}
}
struct WorkoutDetailsView: View {
var workoutID: String!
#StateObject private var viewModel = WorkoutDetailsViewModel()
var workout: HKWorkout
var dateFormatter: DateFormatter
#State private var offset = 0
#State private var isShowingOverlay = false
#State private var editingIndex: Int?
#EnvironmentObject var settingsManager: SettingsManager
#Environment(\.dismiss) private var dismiss
var body: some View {
if viewModel.sppWorkout != nil {
VStack {
ListWorkoutItem(workout: workout, dateFormatter: dateFormatter)
.padding([.leading, .trailing], 10.0)
List(viewModel.sppWorkout.exercises, id: \.id) { exercise in
let index = viewModel.sppWorkout.exercises.firstIndex(of: exercise) ?? 0
DetailListSetItem(exerciseSet: viewModel.sppWorkout.exercises[index], set: index + 1)
.environmentObject(settingsManager)
.swipeActions {
Button(role: .destructive, action: {
viewModel.sppWorkout.exercises.removeAll(where: { $0 == viewModel.sppWorkout.exercises[index]})
} ) {
Label("Delete", systemImage: "trash")
}
Button(role: .none, action: {
isShowingOverlay = true
editingIndex = index
} ) {
Label("Edit", systemImage: "pencil")
}.tint(.blue)
}
}
.padding([.leading, .trailing], -30)
//iOS 16 .scrollContentBackground(.hidden)
}
.overlay(alignment: .bottom, content: {
editOverlay
.animation(.easeInOut (duration: 0.5), value: isShowingOverlay)
})
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action : {
do {
try FileIOManager.write(viewModel.sppWorkout, toDocumentNamed: "\(viewModel.sppWorkout.id ?? 0).json")
} catch {
Debugger.log(error: error.localizedDescription)
}
dismiss()
}){
Image(systemName: "arrow.left")
})
} else {
Text("No workout details found")
.italic()
.fontWeight(.bold)
.font(.system(size: 35))
.onAppear(perform: {
viewModel.setupData(with: workoutID)
})
}
}
#ViewBuilder private var editOverlay: some View {
if isShowingOverlay {
ZStack {
Button {
isShowingOverlay = false
} label: {
Color.clear
}
.edgesIgnoringSafeArea(.all)
VStack{
Spacer()
EditSetPopup(isShowingOverlay: $isShowingOverlay,
update: { reps, weight in
guard let editingIndex = editingIndex else { return }
print(viewModel.sppWorkout.exercises)
print("dupa aia:\n")
viewModel.update(editingIndex, newReps: Int(reps) ?? 0, newWeight: Double(weight) ?? 0.0)
print(viewModel.sppWorkout.exercises)
self.editingIndex = nil
})
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color("popupBackground"),
lineWidth: 3)
)
}
}
}
}
}
So I got a very good explanation on reddit on what causes the problem. Thank you u/neddy-seagoon if you are reading this.
The explanation
. I believe that updating an array will not trigger a state update. The only thing that will, with an array, is if the count changes. So
sppWorkout.exercises[index].reps = newReps
will not cause a trigger. This is not changing viewModel.sppWorkout.exercises.indices
So all I had to to was modify my List from
List(viewModel.sppWorkout.exercises, id: \.id)
to
List(viewModel.sppWorkout.exercises, id: \.hashValue)
as this triggers the list update because the hashValue does change when updating the properties of the entries in the list.
For the line
List(viewModel.sppWorkout.exercises, id: \.id) { exercise in
Replace with
List(viewModel.sppWorkout.exercises, id: \.self) { exercise in
I was learning S.O.L.I.D right now from the book iOS programming Big Nerd Ranch. I want to make a an array from convenience init but I have a problem, I want to display the text and image like the exact order of an array but I don't know how. here I show you my code
This is my item class
class Item: NSObject {
var imageName: String
var label: String
init(imageName: String, label: String) {
self.imageName = imageName
self.label = label
super.init()
}
convenience init(list: Bool = false) {
if list {
let imageList = ["milada-vigerova", "david-rodrigo", "quran"]
let labelList = ["Fiqih", "Hadist", "Tafsir"]
let sortImageName = imageList[imageList.count - 1]
let sortLabel = labelList[labelList.count - 1]
self.init(imageName: sortImageName, label: sortLabel)
} else {
self.init(imageName: "", label: "")
}
}
}
this my ItemStore class that create an array from Item class
class ItemStore {
var allItems = [Item]()
#discardableResult func createItem() -> Item {
let newItem = Item(list: true)
allItems.append(newItem)
return newItem
}
// I make this for in loop to make the table view numberOfSection will return 3 of an allItems
init() {
for _ in 0..<3 {
createItem()
}
}
}
Please help me
Good Lord, this is pretty nerdy and cumbersome code.
First of all a struct is sufficient, you get the initializer for free
struct Item {
let imageName: String
let label: String
}
In ItemStore create the array instantly
class ItemStore {
var allItems = [Item]()
func createArray() {
allItems = [Item(name: "Fiqih", image: "milada-vigerova"),
Item(name: "Hadist", image: "david-rodrigo"),
Item(name: "Tafsir", image: "quran")]
}
}
In the table view return allItems.count in numberOfRows (not numbersOfSection)
Please refer the following code:
import UIKit
struct Item {
var brandId = 1
var name: String = ""
}
struct Store {
var areaName = ""
var name: String = ""
}
let itemArray = [Item(brandId: 1, name: "item1"), Item(brandId: 2, name: "item2"), Item(brandId: 1, name: "item3") ]
let storeArray = [Store(areaName: "hk", name: "store1"), Store(areaName: "bj", name: "store2"), Store(areaName: "hk", name: "store3")]
var intKeys = [Int]()
var groupedItems = [[Item]]()
var stringKeys = [String]()
var groupedStores = [[Store]]()
extension Array {
func transTo2d() -> [[Element]] {
let grouped = [[Element]]()
return grouped
}
}
itemArray.forEach { (item) in
let brandId = item.brandId
if !intKeys.contains(brandId) {
intKeys.append(brandId)
var newArray = [Item]()
newArray.append(item)
groupedItems.append(newArray)
} else {
let index = intKeys.index(of: brandId)!
groupedItems[index].append(item)
}
}
My final goal is could using itemArray.transTo2d() get a 2d array based on item's brandId, using storeArray.transTo2d() get a 2d array based on store's areaName. I don't how to generic the function that trans 1d array to a 2d array based on the key?
I don't think you can write a generic extension for an Array where the elements will either be of type Item or Store since both of them don't share any relation for you to write a common generic method. You can write extensions for Array where the elements will be of the mentioned type. You just need to conform both of your structs to the equatable protocol.
struct Item {
var brandId = 1
var name: String = ""
}
extension Item : Equatable{
static func ==(lhs: Item, rhs: Item) -> Bool{
return lhs.brandId == rhs.brandId
}
}
struct Store {
var areaName = ""
var name: String = ""
}
extension Store : Equatable{
static func ==(lhs: Store, rhs: Store) -> Bool{
return lhs.areaName == rhs.areaName
}
}
extension Array where Element == Store{
func transform()->[[Store]]{
var storeArray = self
var groupedArray = [[Store]]()
while storeArray.count > 0{
if let firstElement = storeArray.first{
groupedArray.append(storeArray.filter{$0.areaName == firstElement.areaName})
storeArray = storeArray.filter{$0.areaName != firstElement.areaName}
}
}
return groupedArray
}
}
extension Array where Element == Item{
func transform()->[[Item]]{
var itemArray = self
var groupedArray = [[Item]]()
while itemArray.count > 0{
if let firstElement = itemArray.first{
groupedArray.append(itemArray.filter{$0.brandId == firstElement.brandId})
itemArray = itemArray.filter{$0.brandId != firstElement.brandId}
}
}
return groupedArray
}
}
Using the transform function
let storeArray = [Store(areaName: "hk", name: "store1"), Store(areaName: "bj", name: "store2"), Store(areaName: "hk", name: "store3")]
let itemArray = [Item(brandId: 1, name: "item1"), Item(brandId: 2, name: "item2"), Item(brandId: 1, name: "item3") ]
print(storeArray.transform())
print(itemArray.transform())
This will print this output which is what I believe you wanted.
[[Store(areaName: "hk", name: "store1"), Store(areaName: "hk", name: "store3")], [Store(areaName: "bj", name: "store2")]]
[[Item(brandId: 1, name: "item1"), Item(brandId: 1, name: "item3")], [Item(brandId: 2, name: "item2")]]
I have a problem to make a JSON from an array of struct in Swift3. I searched in Stack Overflow, nothing help me (here the screenshot). I have a struct like this:
public struct ProductObject {
var prodID: String
var prodName: String
var prodPrice: String
var imageURL: String
var qty: Int
var stock: String
var weight: String
init(prodID: String, prodName: String, prodPrice: String, imageURL: String, qty: Int, stock: String, weight: String){
self.prodID = prodID
self.prodName = prodName
self.prodPrice = prodPrice
self.imageURL = imageURL
self.qty = qty
self.stock = stock
self.weight = weight
}
}
and the array of that struct:
private var productsArray = [ProductObject]()
When the array is not empty, and then I tried to print it in another class, it shows this in debugger:
[app.cartclass.ProductObject(prodID: "2", prodName: "produk 2", prodPrice: "IDR 1000000", imageURL: "someURL", qty: 1, stock: "11", weight: "200")]
The array is not a valid JSON object. How to make it a valid JSON object? And I wonder whether this part "app.cartclass.ProductObject" is a problem or not to make it a valid JSON object?
edit:
Here's how I serialize into a JSON:
var products = [String:Any]()
for j in 0 ..< cart.numberOfItemsInCart() {
products=["\(j)":cart.getAllProduct(atIndex: j)]
}
if let valid = JSONSerialization.isValidJSONObject(products) {
do {
let jsonproducts = try JSONSerialization.data(withJSONObject: products, options: .prettyPrinted) as! [String:Any]
//print(jsonproducts)
} catch let error as NSError {
print(error)
}
} else {
print("it is not a valid JSON object");
}
If you want to make JSON from custom object then first you need to convert your custom object to Dictionary, so make one function like below in your ProductObject struct.
func convertToDictionary() -> [String : Any] {
let dic: [String: Any] = ["prodID":self.prodID, "prodName":self.prodName, "prodPrice":self.prodPrice, "imageURL":self.imageURL, "qty":qty, "stock":stock, "weight":weight]
return dic
}
Now use this function to generate Array of dictionary from Array of custom object ProductObject.
private var productsArray = [ProductObject]()
let dicArray = productsArray.map { $0.convertToDictionary() }
Here dicArray is made of type [[String:Any]], now you can use JSONSerialization to generate JSON string from this dicArray.
if let data = try? JSONSerialization.data(withJSONObject: dicArray, options: .prettyPrinted) {
let str = String(bytes: data, encoding: .utf8)
print(str)
}
I have an array of object.
I want to get distinct elements in this array by comparing objects based on its name property
class Item {
var name: String
init(name: String) {
self.name = name
}
}
let items = [Item(name:"1"), Item(name:"2"), Item(name:"1"), Item(name:"1"),Item(name:"3"), Item(name:"4")]
result:
let items = [Item(name:"1"), Item(name:"2"),Item(name:"3"), Item(name:"4")]
how can I do this in swift?
Here is an Array extension to return the unique list of objects based on a given key:
extension Array {
func unique<T:Hashable>(by: ((Element) -> (T))) -> [Element] {
var set = Set<T>() //the unique list kept in a Set for fast retrieval
var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
for value in self {
if !set.contains(by(value)) {
set.insert(by(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
For your example you can do:
let uniqueBasedOnName = items.unique{$0.name}
Hope this will help you:
class Item:Equatable, Hashable {
var name: String
init(name: String) {
self.name = name
}
var hashValue: Int{
return name.hashValue
}
}
func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.name == rhs.name
}
let items = [Item(name:"1"), Item(name:"2"), Item(name:"1"), Item(name:"1"),Item(name:"3"), Item(name:"4")]
var uniqueArray = Array(Set(items))
In Swift you can use Equatable protocol to distinguish unique element in an Array of object.
struct Item:Equatable{
var name:String
var price:Double
init(name:String,price:Double) {
self.name = name
self.price = price
}
static func ==(lhs: Item, rhs: Item) -> Bool{
return lhs.name == rhs.name
}
}
class ViewController: UIViewController {
var books = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
items.append(Item(name: "Example 1", price: 250.0))
items.append(Item(name: "Example 2", price: 150.0))
items.append(Item(name: "Example 1", price: 150.0))
items.append(Item(name: "Example 1", price: 150.0))
items.append(Item(name: "Example 3", price: 100.0))
items.unique().forEach { (item) in
print(item.name)
}
}
}
extension Sequence where Iterator.Element: Equatable {
func unique() -> [Iterator.Element] {
return reduce([], { collection, element in collection.contains(element) ? collection : collection + [element] })
}
}
I used the sweet answer of #Ciprian Rarau and then realised I don't even need to add the elements in the first place if they are not unique. So I wrote a little extension for that (inspired by the answer).
extension Array {
public mutating func appendIfUnique<T: Hashable>(_ newElement: Element, check property: ((Element) -> (T))) {
for element in self {
if property(element) == property(newElement) { return }
}
append(newElement)
}
}
Append elements only if unique:
array.appendIfUnique(newElement, check: { $0.name })
Solution that uses keypaths instead of closures:
extension Sequence {
func uniqued<Type: Hashable>(by keyPath: KeyPath<Element, Type>) -> [Element] {
var set = Set<Type>()
return filter { set.insert($0[keyPath: keyPath]).inserted }
}
}
Example for struct Mock { var uuid: UUID; var num: Int }
let uuid = UUID()
let arr = [
Mock(uuid: uuid, num: 1),
Mock(uuid: uuid, num: 2),
Mock(uuid: UUID(), num: 3)
]
let unique = arr.uniqued(by: \.uuid)
unique array will contain first (num = 1) and last (num = 3) elements.