Issue using ScrollView with Array - ios

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)")
}
}

Related

Binding a Stepper with a Dictionary SwiftUI

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
)
}
}
}
}

Dynamically create section for list in SwiftUI

I am trying to dynamically create sections for List with a header in SwiftUI.
here is my array:
var lists = [a list of names with A to Z] // array of strings
then I try to get first letter:
var firstCharacters: [Character] {
var firstCharacters = [Character]()
for list in lists.sorted(by: {$0 < $1}) {
if let character = list.first, !firstCharacters.contains(character) {
firstCharacters.append(character)
}
}
return firstCharacters
}
I created the list like this:
List {
ForEach(firstCharacters, id: \.self) { charachter in
Section(header: Text("\(charachter.description)")) {
ForEach(Array(lists.enumerated()), id:\.element) { index, element in
Text("Name \(element), Id: \(index)")
})
}
}
}
}
Now I have a problem adding section to the list now sure how can I combine with the list.
A better way to section this is to take your list of words and turn it into a dictionary keyed by the first letter like this:
var wordDict: [String:[String]] {
let letters = Set(lists.compactMap( { $0.first } ))
var dict: [String:[String]] = [:]
for letter in letters {
dict[String(letter)] = lists.filter( { $0.first == letter } ).sorted()
}
return dict
}
Then use the Dict in your List like this:
List {
// You then make an array of keys, sorted by lowest to highest
ForEach(Array(wordDict.keys).sorted(by: <), id: \.self) { character in
Section(header: Text("\(character)")) {
// Because a dictionary lookup can return nil, we need to provide a response
// if it fails. I used [""], though I could have just force unwrapped.
ForEach(wordDict[character] ?? [""], id: \.self) { word in
Text("Name \(word)")
}
}
}
}
This prevents you from having to iterate through the whole list for every letter. It is done once when creating the Dict. You no longer need var firstChar.
The following would print the names in each section:
struct ContactsView: View {
let names = ["James", "Steve", "Anna", "Baxter", "Greg", "Zendy", "Astro", "Jenny"]
var firstChar: [Character] {
let array = names
.compactMap({ $0.first })
// Set is just a quick way to remove duplicates.
return Array(Set(array))
.sorted(by: { $0 < $1 })
}
var body: some View {
List {
ForEach(firstChar, id: \.self) { char in
Section(header: Text("\(char.description)")) {
ForEach(names, id: \.self) { name in
if name.first == char {
Text(name)
}
}
}
}
}
}
}
Although a better way might be to not have 2 different arrays but merge them into one where the first letter is the key and the values are the names.
That way you don't have to iterate over every name for every section.
My recommendation is a view model which groups an array of strings into a Section struct with Dictionary(grouping:by:)
class ViewModel : ObservableObject {
struct Section: Identifiable {
let letter : String
let names : [String]
var id : String { letter }
}
#Published var sections = [Section]()
var names = [String]() {
didSet {
let grouped = Dictionary(grouping: names, by: {$0.prefix(1)})
sections = grouped.keys.sorted().map{Section(letter: String($0), names: grouped[$0]!)}
}
}
}
Whenever the content of the array is being modified the section array (and the view) is updated.
In the view in onAppear pass some names
struct ContentView: View {
#StateObject private var model = ViewModel()
var body: some View {
List(model.sections) { section in
Section(section.letter) {
ForEach(section.names, id: \.self, content: Text.init)
}
}
.onAppear {
model.names = ["Aaran", "Aaren", "Aarez", "Badsha", "Bailee", "Bailey", "Bailie", "Bailley", "Carlos", "Carrich", "Carrick", "Carson", "Carter", "Carwyn", "Dante", "Danyal", "Danyil", "Danys", "Daood", "Dara", "Darach", "Daragh", "Darcy", "D'arcy", "Dareh", "Eisa", "Eli", "Elias", "Elijah", "Eliot", "Elisau", "Finn", "Finnan", "Finnean", "Finnen", "Finnlay", "Geoff", "Geoffrey", "Geomer", "Geordan", "Hamad", "Hamid", "Hamish", "Hamza", "Hamzah", "Han", "Idris", "Iestyn", "Ieuan", "Igor", "Ihtisham", "Jarno", "Jarred", "Jarvi", "Jasey-Jay", "Jasim", "Jaskaran", "Jason", "Jasper", "Jaxon", "Kabeer", "Kabir", "Kacey", "Kacper", "Kade", "Kaden", "Kadin", "Kadyn", "Kaeden", "Kael", "Kaelan", "Kaelin", "Kaelum", "Kai", "Kaid", "Kaidan", "Kaiden", "Kaidinn", "Kaidyn", "Kaileb", "Kailin", "Karsyn", "Karthikeya", "Kasey", "Kash", "Kashif", "Kasim", "Kasper", "Kasra", "Kavin", "Kayam", "Leiten", "Leithen", "Leland", "Lenin", "Lennan", "Lennen", "Lennex", "Lennon", "Lennox", "Lenny", "Leno", "Lenon", "Lenyn", "Leo", "Leon", "Leonard", "Leonardas", "Leonardo", "Lepeng", "Leroy", "Leven", "Levi", "Levon", "Machlan", "Maciej", "Mack", "Mackenzie", "Mackenzy", "Mackie", "Macsen", "Macy", "Madaki", "Nickson", "Nicky", "Nico", "Nicodemus", "Nicol", "Nicolae", "Nicolas", "Nidhish", "Nihaal", "Nihal", "Nikash", "Olaoluwapolorimi", "Ole", "Olie", "Oliver", "Olivier", "Peter", "Phani", "Philip", "Philippos", "Phinehas", "Phoenix", "Phoevos", "Pierce", "Pierre-Antoine", "Pieter", "Pietro", "Piotr", "Porter", "Prabhjoit", "Prabodhan", "Praise", "Pranav", "Rasul", "Raul", "Raunaq", "Ravin", "Ray", "Rayaan", "Rayan", "Rayane", "Rayden", "Rayhan", "Santiago", "Santino", "Satveer", "Saul", "Saunders", "Savin", "Sayad", "Sayeed", "Sayf", "Scot", "Scott", "Scott-Alexander", "Seaan", "Seamas", "Seamus", "Sean", "Seane", "Sean-James", "Sean-Paul", "Sean-Ray", "Seb", "Sebastian", "Sebastien", "Selasi", "Seonaidh", "Sephiroth", "Sergei", "Sergio", "Seth", "Sethu", "Seumas", "Shaarvin", "Shadow", "Shae", "Shahmir", "Shai", "Shane", "Shannon", "Sharland", "Sharoz", "Shaughn", "Shaun", "Tadhg", "Taegan", "Taegen", "Tai", "Tait", "Uilleam", "Umair", "Umar", "Umer", "Umut", "Urban", "Uri", "Usman", "Uzair", "Uzayr", "Valen", "Valentin", "Valentino", "Valery", "Valo", "Vasyl", "Vedantsinh", "Veeran", "Victor", "Victory", "Vinay", "Vince", "Wen", "Wesley", "Wesley-Scott", "Wiktor", "Wilkie", "Will", "William", "William-John", "Willum", "Wilson", "Windsor", "Wojciech", "Woyenbrakemi", "Wyatt", "Wylie", "Wynn", "Xabier", "Xander", "Xavier", "Xiao", "Xida", "Xin", "Xue", "Yadgor", "Yago", "Yahya", "Yakup", "Yang", "Yanick", "Yann", "Yannick", "Yaseen", "Yasin", "Yasir", "Yassin", "Yoji", "Yong", "Yoolgeun", "Yorgos", "Youcef", "Yousif", "Youssef", "Yu", "Yuanyu", "Yuri", "Yusef", "Yusuf", "Yves", "Zaaine", "Zaak", "Zac", "Zach", "Zachariah", "Zacharias", "Ziyaan", "Zohaib", "Zohair", "Zoubaeir", "Zubair", "Zubayr", "Zuriel"]
}
}
}

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
...
}

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)
}
}
}
}

Swift Struct unable to set array elements above 0

I have following Swift struct:
struct MainStruct : Decodable{
var array : [InternalArray]?
}
struct InternalArray : Decodable{
var firstName : String?
var lastName : String?
var Number : Int?
}
And this is how I'm using it:
var testing: MainStruct? = MainStruct()
testing?.array = []
testing?.array!.append(InternalArray())
testing?.array![0].firstName = "TEST"
testing?.array![1].firstName = "TEST 1" - error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION
It seems to work fine when I'm setting array element [0] but when I try to set element [1] I get an error. Maybe somebody know what is wrong with my implementation ?
The problem is you only have 1 item (index 0).
Let's unpack this, first we unwrap your option MainStruct in the testing variable, and unwrap the optional array:
if let unwrappedTestingMainStruct = testing,
let unwrappedArray = unwrappedTestingMainStruct.array {
unwrappedArray.count // = 1
}
You then try to access testing?.array![1] which would be the second item in the array… which doesn't exist.
If you just had the line:
testing?.array![1]
you would see the same error

Resources