Formatting Custom Grid View to Periodic Table in Swift? - arrays

How can I reformat the code of GridStack in the view body so I may duplicate it per row, such as 1 row of 2 columns, 2 rows of 8 columns, 4 rows of 18 columns, 2 rows of 15 columns?, I am cutting out the holes with this Hypothesis to shape an Interactive Periodic Table, refer to image attached.
#jnpdx has provided an example for display function per cell coordinate, along with this i will need an ontapgesture to operate the overlay of data per cell so I may send information to other menu fields.
#jnpdx so now right before the roundedrectangle in this edit and turn on the display function commented out, i need to some how over lay the custom data per cell instead of hydrogen on every cell plus create an ontapgesture to send globally to other menu fields in the application?
struct GridStack<Content: View>: View {
let rows: Int
let columns: Int
let content: (Int, Int) -> Content
//func shouldDisplayAtCoord(row: Int, column: Int) { if row == 0 && column > 1 { return true } }
var body: some View {
VStack {
ForEach(0 ..< rows, id: \.self) { row in
HStack {
ForEach(0 ..< columns, id: \.self) { column in
ZStack{
RoundedRectangle(cornerRadius: 5)
.fill(Color.brown)
.frame(width: 40, height: 50)
Image("RadicalDeepscale30").opacity(0.4)
content(row, column)
}
}
}
}
}
}
init(rows: Int, columns: Int, #ViewBuilder content: #escaping (Int, Int) -> Content) {
self.rows = rows
self.columns = columns
self.content = content
}
}
// An example view putting GridStack into practice.
struct DETEView: View {
var body: some View {
VStack(alignment: .center) {
Text("DART Edge Table of Elements")
.font(.largeTitle)
.bold()
//.colorInvert()
.padding(.top, 20)
.shadow(radius: 3)
}
VStack(alignment: .center) {
HStack(alignment: .center){
VStack(alignment: .center) {
GridStack(rows: 9, columns: 18) { row, col in
VStack(alignment: .leading){
HStack(alignment: .top) {
VStack(alignment: .leading) {
Text("1")
.font(.system(size: 9))
// .bold()
//.shadow(radius: 1)
}
}
HStack(alignment: .center) {
VStack(alignment: .center){
Text("H")
.font(.system(size: 12))
.bold()
//.shadow(radius: 1)
Text("Hydrogen")
.font(.system(size: 7))
//.bold()
// .shadow(radius: 1)
//.padding(.top, 1)
}
}
}
}
}
}
}.frame(width: 950, height: 670, alignment: .top).padding(.top, 20)
}
}

This is a simple implementation using a similar strategy to what we discussed in the comments/chat. In this case, instead of using a function to determine whether the element should be displayed, it just looks it up in a table that lists the elements and stores them based on their coordinates on the grid.
struct GridStack<Content: View>: View {
let rows: Int
let columns: Int
let content: (Int, Int) -> Content
var body: some View {
VStack {
ForEach(0 ..< rows, id: \.self) { row in
HStack {
ForEach(0 ..< columns, id: \.self) { column in
content(row,column)
}
}
}
}
}
}
struct ElementCoordinate: Hashable {
var row : Int
var column: Int
}
struct Element {
var atomicNumber: Int
var name : String
}
let tableOfElements : [ElementCoordinate:Element] = [
ElementCoordinate(row: 1, column: 1):Element(atomicNumber: 1, name: "Hydrogen"),
ElementCoordinate(row: 1, column: 18):Element(atomicNumber: 2, name: "Helium"),
ElementCoordinate(row: 2, column: 1):Element(atomicNumber: 3, name: "Lithium"),
ElementCoordinate(row: 2, column: 2):Element(atomicNumber: 4, name: "Beryllium"),
ElementCoordinate(row: 2, column: 13):Element(atomicNumber: 5, name: "Boron"),
ElementCoordinate(row: 2, column: 14):Element(atomicNumber: 6, name: "Carbon"),
]
struct ContentView: View {
var body: some View {
GridStack(rows: 9, columns: 18) { row, column in
ZStack{
if let element = tableOfElements[ElementCoordinate(row: row + 1, column: column + 1)] {
RoundedRectangle(cornerRadius: 5)
.fill(Color.brown)
Image("RadicalDeepscale30").opacity(0.4)
Text(element.name)
}
}
.frame(width: 40, height: 50)
}
}
}
Note the + 1 on the row and column numbers when querying the grid, since row and column are zero-based in the Swift code, but my tableOfElements starts with an index of 1.
This should show you not only how to do the layout, but how to associate each coordinate with a specific element.
Obviously the implementation details may change to fit your own model, but this should give you an idea of how to get started.

Related

How would I delete items from a list with a button inside that list?

SwiftUI
[This is the code for the list and my button]
ForEach(items, id: \.self) { current in
VStack(alignment: .leading) {
Text("\(current.task)")
.padding(2)
Text("\(dateFormatter.string(from: current.date))")
Button(action: {},
label: {
Image(systemName:
"checkmark.rectangle.fill")
.resizable()
.frame(width: 50, height: 35)
})
}
Items is my array containing a string and a date. Here is the code for it:
#State var items:[Tasks] = [Tasks(task: "Test", date: Date())]
And here is Tasks:
struct Tasks: Hashable {
let task: String
let date: Date
}
This is my list view
I want to have a user be able to click a button under each list item and it will remove that list item. I am currently using the onDelete method but I would prefer to have a confirm button in each list item that would allow the user to remove that list item.
#State var counter = -1
I tried using a counter that would increase by 1 each time the ForEach loop ran and create a new variable inside of that ForEach loop equal to it. However I could not access the variable inside of the button to use as an index.
Using your current code structure, assuming each task: String is unique, you could just add this to your Button action:
Button(action: {
if let index = items.firstIndex(where: {$0.task == current.task}) {
items.remove(at: index)
}
},
label: {
Image(systemName: "checkmark.rectangle.fill")
.resizable()
.frame(width: 50, height: 35)
})
However, it would be more robust to add an id property to your Task, and
at the same time rename it, because Swift already defines Task as part of its async/await framework.
Here is the complete code I use to test my answer:
struct MyTask: Identifiable, Hashable { // <--- here
let id = UUID() // <--- here
let task: String
let date: Date
}
struct ContentView: View {
#State var items:[MyTask] = [
MyTask(task: "Test1", date: Date()),
MyTask(task: "Test2", date: Date().addingTimeInterval(60*60*24*44)),
MyTask(task: "Test3", date: Date().addingTimeInterval(60*60*24*88))
]
var body: some View {
ForEach(items) { current in // <--- here
VStack(alignment: .leading) {
Text(current.task).padding(2) // <--- here
Text(dateFormatter.string(from: current.date)) // <--- here
Button(action: {
// --- here
if let index = items.firstIndex(where: {$0.id == current.id}) {
items.remove(at: index)
}
},
label: {
Image(systemName: "checkmark.rectangle.fill").resizable()
.frame(width: 50, height: 35)
})
}
}
}
var dateFormatter: DateFormatter = {
let frmt = DateFormatter()
frmt.dateFormat = "yyyy-MMM-dd"
return frmt
}()
}

Why do I get error: 'init(_:)' requires that 'Binding<String>' conform to 'StringProtocol' when trying to iterate through a binding object?

I am trying to iterate through an array called: contacts that is an array of Contact (a hashable struct with contact data). This works when I iterate through the array directly (as in, placing the array directly into the Foreach block without using any type of binding) however, when I try to pass in the array via a binding, using the key: $contacts, I get the error: Initializer 'init(_:)' requires that 'Binding<String>' conform to 'StringProtocol'
I feel like the concept is similar but I am still wrapping my head around how swift works. Please see the below code:
Here is where I set up the binding (where the error appears)
struct ContactsGridView: View {
#Binding var contacts: [Contact]
let threeColumnGrid = [GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20)]
var body: some View {
NavigationStack {
VStack {
// Contacts Scroll View
ScrollView {
LazyVGrid(columns: threeColumnGrid, spacing: 20) {
ForEach($contacts, id: \.self) { contact in
VStack {
Circle()
.fill(.white)
Text(contact.firstName) // Here is where I get the error.
}
}
}.padding(EdgeInsets(top: 20,
leading: 20,
bottom: 20,
trailing: 20))
}
}.background(Color(CustomColors.background.rawValue))
}
}
}
And here is where I set up the State:
struct ContactsViewController: View {
#State private var selectedTab: Int = 0
#State private var employeeContacts: [Contact] = employeeContactsTempData
#State private var clientContacts: [Contact] = clientContactsTempData
var body: some View {
NavigationStack {
Divider()
.background(.thinMaterial)
.navigationTitle("Contacts")
VStack {
Picker("", selection: $selectedTab) {
Text("Employees").tag(0)
Text("Clients").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
switch(selectedTab) {
case 0: ContactsGridView(contacts: $employeeContacts)
case 1: ContactsGridView(contacts: $clientContacts)
default:
ContactsGridView(contacts: $employeeContacts)
}
}
}
}
}

Realm count/indices not counting in ForEach

I'm trying to make a view where users can create their own collections, however it seems my ForEach always returns zero list indices BUT when I insert the .count into print its counting correctly, so append is working. How I can fix the cell loop? It was working with non-realm array...
import Foundation
import RealmSwift
final class TagGroup: Object, ObjectKeyIdentifiable {
#objc dynamic var _id = ObjectId.generate()
#objc dynamic var name : String?
var tags = RealmSwift.List<Tag>()
override class func primaryKey() -> String? {
"_id"
}
}
...
import SwiftUI
import RealmSwift
struct CreateTags: View {
var tagGroup = TagGroup()
var columns: [GridItem] =
Array(repeating: .init(.flexible(), spacing: 8), count: 2)
var body: some View {
NavigationView {
ScrollView {
VStack(alignment: .leading) {
LazyVGrid(columns: columns, alignment: .trailing, spacing: 8) {
//this code should draw an rectangle for each tag in a list, but it does nothing
ForEach(tagGroup.tags.indices, id: \.self) { tagIndex in
VStack {
ZStack {
RoundedRectangle(cornerRadius: 16)
.foregroundColor(Color(UIColor.systemGray6))
}
.aspectRatio(1.5, contentMode: .fit)
}
}
VStack {
Button(action: {
tagGroup.tags.append(Tag())
})
...

Looping multiple arrays with ForEach SwiftUI

UI: https://imgur.com/a/0BbJBFc
I'm using the ForEach to iterate over an array of minerals in the example code, but I can't find a proper solution to loop the second array (mineral amount) right underneath the minerals.
In different project, I made it so far that the ForEach loops both arrays but for every mineral displays all amount for the Planet and for the second mineral all amount for the Planet and so on.
I did create a new struct with both arrays but without success. Adding a binding property also failed. I hope to learn a new swift method to achieve the desire look.
Data File
import Foundation
struct Planet: Identifiable {
var id = UUID()
var name: String
var minerals: [String]
var mineralAmount: [String]
}
let planetsData: [Planet] = [
Planet(name: "Jupiter", minerals: ["Iron", "Gold", "Copper"], mineralAmount: ["10K", "20K", "30K"]),
Planet(name: "Earth", minerals: ["Lithium", "Aluminium", "Quartz"], mineralAmount: ["40K", "50K", "60K"])
]
ContentView
import SwiftUI
struct ContentView: View {
var planet: Planet
var body: some View {
VStack() {
ForEach(planet.minerals, id: \.self) { item in
Text(item)
.font(.system(size: 22, weight: .regular))
.foregroundColor(.primary)
Text("amount to be added")
.font(.system(size: 22, weight: .regular))
.foregroundColor(.primary)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(planet: planetsData[0])
}
}
[1]: https://i.stack.imgur.com/4MDKs.png
You can achieve it this way:
ScrollView {
VStack (alignment: .leading) {
ForEach(0..<planet.minerals.count) { i in
HStack {
Circle()
.frame(width: 50, height: 50)
.foregroundColor(.black)
VStack (alignment: .leading) {
Text(planet.minerals[i])
.font(.system(size: 22, weight: .regular))
.foregroundColor(.primary)
Text(planet.mineralAmount[i])
.font(.system(size: 22, weight: .regular))
.foregroundColor(.secondary)
}
}
}
Spacer()
}
}
why don't you create a dictionary from both values
mineralsDic = [minerals: mineralAmount]
I of course know the syntax of dictionary but I just try to explain my idea + instead of making 2 loops you can make only one which has less complexity and better performance

on TabGesture Swift UI

look for some help on my study of the scrollView in swiftUI.
I have a scroll view that display the value of an array, base when the user tap on the different item of scroll view I want to display it on the textField below.
how can I pass the array value to the text field??
import SwiftUI
struct ContentView: View {
let post = ["TEST1 ","Test 2" , "Test 3","TEST4 ","Test 5" , "Test 6"]
var temp = ""
var body: some View {
VStack {
ScrollView(.horizontal, content: {
HStack(spacing: 100) {
ForEach(post, id: \.self){ item in
ZStack {
Rectangle().foregroundColor(.blue).frame(width: 190, height: 170, alignment: .center)
Text(item)
}.onTapGesture {
// pass the value off Scroll View to the text
debugPrint("\(item)")
}
}
}
.padding(.leading, 10)
})
.frame(height: 190)
Spacer()
Text("dispaly here array value selected")
Spacer()
}
}
}
thank for helping me...
The trick here is you need to #State temp when you need to assign to a #State value inside the view.
struct ContentView: View {
let post = ["TEST1 ","Test 2" , "Test 3","TEST4 ","Test 5" , "Test 6"]
#State private var temp = ""
var body: some View {
VStack {
ScrollView(.horizontal, content: {
HStack(spacing: 100) {
ForEach(post, id: \.self){ item in
ZStack {
Rectangle().foregroundColor(.blue).frame(width: 190, height: 170, alignment: .center)
Text(item)
}.onTapGesture {
// pass the value off Scroll View to the text
self.temp = item
}
}
}
.padding(.leading, 10)
})
.frame(height: 190)
Spacer()
Text( self.temp)
Spacer()
}
}
}

Resources