Assigning NavigationLink to Images within Array in Swift UI - arrays

I have an array of images that I am passing through a LazyVgrid.
I am wondering how I assign a NavigationLink to the Image names stored within the array, clicking icon1 navigates to page1, ect for all 8.
------ Swift UI -------
import SwiftUI
struct Grid: View {
var images: [String] = ["icon1", "icon2", "icon3", "icon4",
"icon5", "icon6", "icon7","icon8"]
var columnGrid: [GridItem] = [GridItem(.flexible(), spacing: 25), GridItem(.flexible(), spacing: 25)]
var body: some View {
LazyVGrid(columns: columnGrid, spacing: 50) {
ForEach(images, id:\.self) { image in
Image(image)
.resizable()
.scaledToFill()
.frame(width: (UIScreen.main.bounds.width / 2.75 ) - 1,
height: (UIScreen.main.bounds.width / 2.75 ) - 1)
.clipped()
.cornerRadius(25)
}
}
}
}

For occasions like this I would prefer an enum that holds all related information. You also could do this with a struct the reasoning behind this stays the same.
enum ImageEnum: String, CaseIterable{ //Please find a better name ;)
case image1, image2
var imageName: String{ // get the assetname of the image
switch self{
case .image1:
return "test"
case .image2:
return "test2"
}
}
#ViewBuilder
var detailView: some View{ // create the view here, if you need to add
switch self{ // paramaters use a function or associated
case .image1: // values for your enum cases
TestView1()
case .image2:
TestView2()
}
}
}
struct TestView1: View{
var body: some View{
Text("test1")
}
}
struct TestView2: View{
var body: some View{
Text("test2")
}
}
And your Grid View:
struct Grid: View {
var columnGrid: [GridItem] = [GridItem(.flexible(), spacing: 25), GridItem(.flexible(), spacing: 25)]
var body: some View {
NavigationView{ // Add the NavigationView
LazyVGrid(columns: columnGrid, spacing: 50) {
ForEach(ImageEnum.allCases, id:\.self) { imageEnum in // Itterate over all enum cases
NavigationLink(destination: imageEnum.detailView){ // get detailview here
Image(imageEnum.imageName) // get image assset name here
.resizable()
.scaledToFill()
.clipped()
.cornerRadius(25)
}
}
}
}
}
}
Result:

Related

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

How to handle .onDelete for SwiftUI list array with .reversed()

I am attempting to make a basic SwiftUI list in which each new list item is presented at the top of the list. To do this, I appended .reversed() to the array passed into the ForEach loop, represented by viewModel.itemList. I have also set up .onDelete to handle removing of the list items. However, when I delete an item, such as the last item in the list, it instead deletes the last item in the array (the item at the top of the list). How can I configure .onDelete to delete the correct item when the array is reversed?
See my code below. Thanks!
ContentView
struct ContentView: View {
#StateObject var viewModel = ToDoListViewModel()
#State private var listItemName = ""
var body: some View {
NavigationView {
VStack(alignment: .leading) {
List {
ForEach(viewModel.itemList.reversed()) { item in
Text(item.listItem)
}.onDelete { index in
self.viewModel.itemList.remove(atOffsets: index)
}
}
HStack {
TextField("Enter List Item", text: $listItemName)
Button(action: {
viewModel.addToList(ToDoModel(listItem: listItemName))
listItemName = ""
}) {
Image(systemName: "plus")
.font(.largeTitle)
.frame(width: 75, height: 75)
.foregroundColor(Color.white)
.background(Color.blue)
.clipShape(Circle())
}
}.frame(minWidth: 100, idealWidth: 150, maxWidth: 500, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
.padding(.leading, 16)
.padding(.trailing, 16)
}.navigationBarTitle("To Do List", displayMode: .inline)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Model
struct ToDoModel: Identifiable, Codable {
var id = UUID()
var listItem: String = ""
}
ViewModel
class ToDoListViewModel: ObservableObject {
#Published var itemList = [ToDoModel]()
func addToList( _ item: ToDoModel) {
itemList.append(item)
}
}
you could also try this approach:
.onDelete { index in
// get the item from the reversed list
let theItem = viewModel.itemList.reversed()[index.first!]
// get the index of the item from the viewModel, and remove it
if let ndx = viewModel.itemList.firstIndex(of: theItem) {
viewModel.itemList.remove(at: ndx)
}
}
Caveat: this may not be the most algorithmically efficient method. However, for simple deleting on a List, it should perform fine.
.onDelete { offsets in
let reversed = Array(viewModel.itemList.reversed()) //get the reversed array -- use Array() so we don't get a ReversedCollection
let items = Set(offsets.map { reversed[$0].id }) //get the IDs to delete
viewModel.itemList.removeAll { items.contains($0.id) } //remove the items with IDs that match the Set
}

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

Change button/label text using key:value from local JSON - SwiftUI

New to SwiftUI. Trying to get a JSON key:value array to update to the next random item when the user presses the button. Got it to load up just fine, but the button does nothing. Tried making a shuffle function, but couldn't find a way to pass in the new values to the Text areas. Also tried to make my decodedQuotes and quote variables into #State vars inside the View, but they initialize before self is available.
Could normally call touchesBegan and write a simple function in Storyboard. Is there something similar I could do here?
var decodedQuotes = Bundle.main.decode([Quote].self, from: "quotes.json")
// parses an array with "quote":"name" pairs
var quote = decodedQuotes.randomElement()!
struct QuoteView: View {
var body: some View {
Button(action:
// Need it to update the Text below with a new random item from quote
)
HStack {
VStack {
HStack(alignment: .center) {
Text(quote.quote)
.multilineTextAlignment(.center)
.padding()
.foregroundColor(.black)
}
HStack {
Text("-\(quote.name)")
.foregroundColor(.black)
}
}
}
.frame(width: 300, height: 300, alignment: .center)
.background(Background(isHighlighted: true, shape: Rectangle()))
.foregroundColor(.blue)
.padding(4)
.cornerRadius(20)
}
}
You were on the right track with #State
struct Quote {
var quote : String
var name : String
}
var decodedQuotes = [Quote(quote: "test1", name: "name1"),
Quote(quote: "test2", name: "name2"),
Quote(quote: "test3", name: "name3"),]
struct QuoteView: View {
#State var quote : Quote? = decodedQuotes.randomElement()
var body: some View {
Button(action: {
quote = decodedQuotes.randomElement()
}) {
Text("New quote")
}
if let quote = quote {
HStack {
VStack {
HStack(alignment: .center) {
Text(quote.name)
.multilineTextAlignment(.center)
.padding()
.foregroundColor(.black)
}
HStack {
Text("-\(quote.name)")
.foregroundColor(.black)
}
}
}
.frame(width: 300, height: 300, alignment: .center)
.foregroundColor(.blue)
.padding(4)
.cornerRadius(20)
}
}
}
Obviously, for testing, I just used an array of pre-made Quotes
If you wanted to, you could make decodedQuotes a #State property on the QuoteView as well and decode them in onAppear
I've also chosen to make quote an optional for now. I check to see if it's available by doing the if let quote = quote line. This should be a bit future-proof in case you start loading quotes from other places at some point.
I believe this is a better implementation in the current SwiftUI where the text actually changes within the button. I hope it helps>
import SwiftUI
struct Quote {
var quote : String
var name : String
}
var decodedQuotes = [Quote(quote: "Title 1", name: "Description 1."),
Quote(quote: "Title 2", name: "Second description."),
Quote(quote: "Title 3", name: "final item."),]
struct ContentView: View {
#State var quote : Quote? = decodedQuotes.randomElement()
var body: some View {
Button(action: {
quote = decodedQuotes.randomElement()
}) {
Text("New quote")
if let quote = quote {
HStack {
VStack {
VStack(alignment: .center) {
Text(quote.quote)
.multilineTextAlignment(.center)
.padding()
.foregroundColor(.blue)
Text("-\(quote.name)")
.foregroundColor(.blue)
}
}
}
.frame(width: 300, height: 300, alignment: .center)
.foregroundColor(.blue)
.padding(4)
.cornerRadius(20)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

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