Binding a Stepper with a Dictionary SwiftUI - ios

I'm not sure if this is possible but I'm currently trying to create a list of steppers using a dictionary[String: Int]. Using the stepper I'm hoping to change the qty amount in the dictionary. I tried binding the value to the stepper by first doing $basic[name] and then that didn't work and so I ended up with $basic[keyPath: name] which resulted in fewer errors but still wasn't working. In the beginning I was having problems of not wanting to change the order of the dictionary that I made, and so I ended up with the ForEach below which worked for not changing the order of dictionary, however, I'm wondering if that's one of the reasons that the binding isn't working.
import SwiftUI
struct AllSuppliesStruct {
#State var basic = ["Regular Staples": 0, "Big Staples": 0]
var body: some View {
Form {
//Basic Supplies
ForEach(basic.sorted(by: >), id: \.key) { name, qty in
Stepper("\(name), \(qty)", value: $basic[keyPath: name], in: 0...10)
}
}
}
}
Goal:
If I pressed on the stepper only once for both Regular and Big Staples then I expect this in the dictionary
basic = ["Regular Staples": 1, "Big Staples": 1]

You can manually create a Binding that acts on basic and pass that to Stepper:
struct AllSuppliesStruct: View {
#State var basic = ["Regular Staples": 0, "Big Staples": 0]
var body: some View {
Form {
ForEach(basic.sorted(by: >), id: \.key) { name, qty in
Stepper(
"\(name), \(qty)",
value: .init(
get: { basic[name]! },
set: { basic[name] = $0 }
),
in: 0...10
)
}
}
}
}

Related

Swift - Converting a Dictionary to a KeyValuePair [duplicate]

In Swift Charts the signature for chartForegroundStyleScale to set the ShapeStyle for each data series is:
func chartForegroundStyleScale<DataValue, S>(_ mapping: KeyValuePairs<DataValue, S>) -> some View where DataValue : Plottable, S : ShapeStyle
The KeyValuePairs initialiser (init(dictionaryLiteral: (Key, Value)...)) only takes a variadic parameter so any attempt to initialise a foreground style from an array (in my case <String, Color>) results in the error:
Cannot pass array of type '[(String, Color)]' as variadic arguments of type '(String, Color)'
In my application the names of the chart series are set dynamically from the data so although I can generate a [String : Color] dictionary or an array of (String, Color) tuples I can't see that it's possible to pass either of these into chartForegroundStyleScale? Unless I'm missing something this seems like a odd limitation in Swift charts that the series names need to be hard coded for this modifier?
OK I've found an approach that works as long as an arbitrary limitation to the number of entries is acceptable (example below with max size of 4:
func keyValuePairs<S, T>(_ from: [(S, T)]) -> KeyValuePairs<S, T> {
switch from.count {
case 1: return [ from[0].0 : from[0].1 ]
case 2: return [ from[0].0 : from[0].1, from[1].0 : from[1].1 ]
case 3: return [ from[0].0 : from[0].1, from[1].0 : from[1].1, from[2].0 : from[2].1 ]
default: return [ from[0].0 : from[0].1, from[1].0 : from[1].1, from[2].0 : from[2].1, from[3].0 : from[3].1 ]
}
In my case I know that there won't be more than 20 mappings so this func can just be extended to accommodate that number.
Not ideal, but it works...
You could also pass an array of colors to .chartForegroundStyleScale(range:). As long as you add the colors to the array in the same order you add your graph marks it should work fine.
Not incredibly elegant either, but this approach works with an arbitrary number or entries.
struct GraphItem: Identifiable {
var id = UUID()
var label: String
var value: Double
var color: Color
}
struct ContentView: View {
let data = [
GraphItem(label: "Apples", value: 2, color: .red),
GraphItem(label: "Pears", value: 3, color: .yellow),
GraphItem(label: "Melons", value: 5, color: .green)
]
var body: some View {
Chart {
ForEach(data, id: \.label) { item in
BarMark(
x: .value("Count", item.value),
y: .value("Fruit", item.label)
)
.foregroundStyle(by: .value("Fruit", item.label))
}
}
.chartForegroundStyleScale(range: graphColors(for: data))
}
func graphColors(for input: [GraphItem]) -> [Color] {
var returnColors = [Color]()
for item in input {
returnColors.append(item.color)
}
return returnColors
}
}

Using data in an array to insert elements of an array into another in Swift

Hey so could do with some more experienced eyes looking at this issue I'm having.
I have two arrays of playable cards and non playable cards I need to have them as one array to pass into the game when it loads each level but the location of where the cards are added is important so I've made an array of the locations I want them inserted.
Here is my Card model and Level model with an example data
struct Card {
var content: String
var id: Int
var isFaceUp = false
var isMatched = false
}
struct Level {
let levelNumber: String
let numberOfNonCards: Int
let numberOfPlayableCards: Int
let locationsToPlacePlayableCards: [Int]
let winningScore: Int
}
static let levels = [
Level(levelNumber: "one", numberOfNonCards: 20, numberOfPlayableCards: 10, locationsToPlacePlayableCards: [3, 4, 6, 7, 9, 12, 20, 21, 22, 23], winningScore: 5) ]
}
and below is me trying to get this to work.
private static func createCardGame(level: Level) -> [Card] {
var cards = [Card]()
var playableCards = [Card]()
for indexPath in 0..<level.numberOfNonCards {
cards.append(Card(content: catCards[0], id: indexPath*3))
}
for indexPath in 0..<level.numberOfPlayableCards {
playableCards.append(Card(content: catCards[indexPath], id: indexPath*2))
playableCards.append(Card(content: catCards[indexPath], id: indexPath*2 + 5))
}
playableCards.shuffle()
var playingCardLocations = level.locationsToPlacePlayableCards
for indexPath in 0..<playingCardLocations.count {
cards.insert(contentsOf: playableCards[indexPath], at: playingCardLocations[indexPath])
}
return cards
}
This isn't working as I have to make Card conform to Collection which seems to then require it conforms to Sequence and then IteratorProtocol. So wondering what's the best way to implement those protocols? Because can't find much about them? Or is there a better way of doing this in your experience? Would really appreciate your help and input.
insert(contentsOf:at:) expects to be passed a collection of elements to be inserted but it's only being passed a single card.
There's another version of insert that takes a single element. The only change is removing contentsOf.
cards.insert(playableCards[indexPath], at: playingCardLocations[indexPath])
https://developer.apple.com/documentation/swift/array/3126951-insert
Another option is to put the card into an array before inserting it, but it's unnecessary when there's a method that takes a single value.
cards.insert(contentsOf: [playableCards[indexPath]], at: playingCardLocations[indexPath])

Converting Array into Object

I have a list that's organized like : ["ImageUrl","Year","Credit","ImageUrl","Year","Credit"...] and I want to display a horizontal scrollview of the images with the year/credit below. I've tried using a while loop within SwiftUI like this but I receive an error stating the Closure containing control flow statement cannot be used with function builder 'ViewBuilder'.
Here's my code:
struct ImageList : View {
var listOfImages : Array<String>
#State var i = 0
var body: some View{
VStack{
while i < listOfImages.count {
VStack(){
KFImage(listOfImages[i]).resizable().frame(width: 200, height: 300).aspectRatio(contentMode: .fit)
Text(listOfImages[i+1])
Text(listOfImages[i+2])
}
i = i+3
}
}
}
}
I am unable to update how the list's are organized because it's already coming from our backend. My initial plan was to import the list elements into a list of objects like this:
struct HistoricalImages: Hashable {
let link : String
let year : String
let credit : String
}
but i'm not sure how to convert it effectively. Any help is appreciated. This is my first StackOverflow post so please let me know if anything needs to be added!
Use an index range to slice the array to groups of 3 elements and create an object of each slice
var index = 0
var items = [HistoricalImages]()
while index < array.count {
let end = index + 2
if end > array.count {
break
}
let slice = Array(array[index...end])
items.append(HistoricalImages(link: slice[0], year: slice[1], credit: slice[2]))
index += 3
}
You can't use a while loop within the body property:
while i < listOfImages.count {
...
}
You need to use a ForEach instead:
ForEach(0..<listOfImages.count) { idx in
...
}

Issue using ScrollView with Array

I'm trying to use an array to display some game info whenever an array gets updated by a call to a web service. The array is populating fine, but at runtime I get:
Generic struct 'ForEachWithIndex' requires that 'Binding<[MatchData]>'
conform to 'RandomAccessCollection'
My ScrollView:
ScrollView{
ForEach(GameCenterHelper.helper.$MatchList, id: \.self) {row in
ext("\(row.id)")
}
}
My declaration of the array:
#State var MatchList: [MatchData] = [MatchData]()
And MatchData:
class MatchData : Identifiable {
var id: String
var LocalPlayer: Player
var RemotePlayer: Player
init(_ match: GKTurnBasedMatch) {
let local = match.participants[0].player!
let remote = match.participants[1].player ?? GKPlayer()
self.id = match.matchID
LocalPlayer = Player(Alias: local.alias
, DisplayName: local.displayName
, TeamPlayerId: local.teamPlayerID
, PlayerId: local.gamePlayerID
)
RemotePlayer = Player(Alias: remote.alias
, DisplayName: remote.displayName
, TeamPlayerId: remote.teamPlayerID
, PlayerId: remote.gamePlayerID
)
}
}
I have to admit, this is the first time I've used state to try and refresh a ScrollView, but I am finding it much harder than I expected and I am not sure what I have gotten wrong in my code.
You don't need binding to iterate MatchList in ForEach, access it directly
ScrollView{
ForEach(GameCenterHelper.helper.MatchList, id: \.self) {row in // << no $
ext("\(row.id)")
}
}

List in SwiftUI won't show up

I'm currently writing code for a scrollable sideways calendar that displays dates horizontally. I currently have the following code (this is a very simplified version):
struct ScrollableCalendar: View {
var body: some View {
var someArray = [["May", "10", "2020"],["May", "11", "2020"],["May", "12", "2020"]]
ScrollView(.horizontal, showsIndicators: false) {
CalendarDateHorizBase(dates: someArray)
}
}
}
struct CalendarDateHorizBase: View {
var dates: Array<Array<String>>
var body: some View {
HStack {
****THE LOGIC ERRROR OCCURS IN THIS LIST****
List(dates, id: \.description) { date in
CalendarDate(month: date[0], day: date[1], year: date[2])
}
}
}
}
*** CalendarDate() is another view that takes a month, day, and year (all strings) and displays them nicely. The error is not related to CalendarDate()***
When I attempt to hardcode the elements without the List, everything displays fine. However, when I use the List, the screen becomes completely blank. I have no idea why. Does anyone have any ideas? Thanks!
Your dates need to be a proper model as its not identifiable by List.
And also I believe you have some solid reason for using HStack, as I hope you know that list only goes vertically.
The issue is with the data. When you need to show a list of data in a List, each element of the data should be Identifiable. One way to achieve that is to define a structure for you data and make it identifiable:
struct DateEvent: Identifiable {
let id: String
let month: String
let day: String
let year: String
init(rawValue: [String]) {
month = rawValue[0]
day = rawValue[1]
year = rawValue[2]
id = rawValue.joined(separator: "/")
}
}
So now you can use it to build up your List
struct CalendarDateHorizBase: View {
var dates: [[String]] // same as 'Array<Array<String>>' but more swifty style
var body: some View {
HStack {
List(dates.map { DateEvent(rawValue: $0) }) { date in
CalendarDate(month: date.month, day: date.day, year: date.year)
}
}
}
}

Resources