swiftui - save a view to a variable - swiftui

I have the following:
struct Event: Identifiable {
let id: Int
let name: String
let image: String
}
let events = [
Event(id: 0, name: "Host Tournament", image: "cup"),
Event(id: 1, name: "Post Club Info", image: "shield"),
Event(id: 3, name: "Share A Post", image: "write")
]
I want to be able for each Event to hold a separate view.
For example Event(id: 0, name: "Host Tournament", image: "cup", destinationView: PostView())
let events = [
Event(id: 0, name: "Host Tournament", image: "cup",destinationView: PostView()),
Event(id: 1, name: "Post Club Info", image: "shield",destinationView: ClubView()),
Event(id: 3, name: "Share A Post", image: "write", destinationView: StoryView())
]
So i can pass destinationView into my NavigationLink when i loop through events. Im not sure what type the PostView() should be defined as in my struct?
This is what Im currently doing:
ForEach(events) { event in
NavigationLink(destination: //PASS VIEW HERE FROM EVENT) {
VStack {
Image(event.image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.padding(55)
Text(event.name)
.font(.system(.headline))
.padding(.bottom,20)
}
.padding()
.border(Color.black, width: 4)
.cornerRadius(10)
}.buttonStyle(PlainButtonStyle())
}
I want to be able to pass in a view depending on the Event its looping through.

That's pure design to couple model with view so tightly... but if you want, technically it is possible to do in the following way
let events = [
Event(id: 0, name: "Host Tournament", image: "cup",
destinationView: AnyView(PostView())),
Event(id: 1, name: "Post Club Info", image: "shield",
destinationView: AnyView(ClubView())),
Event(id: 3, name: "Share A Post", image: "write",
destinationView: AnyView(StoryView()))
]

Related

How to create a ListView that can be scrolling by group

I would like to be able to scroll to the top of the each List section which is grouped as follows
I am thinking of using ScrollViewReader to scroll to an arbitrary date group using the id specified for each date group as shown below, but it does not work. Please let me know.
Preview
import SwiftUI
struct ScrollableView: View {
private var browseGroupedByDate = BrowseGroupedByDate.all()
var body: some View {
ScrollViewReader { scrollProxy in
VStack {
ScrollView {
ForEach(browseGroupedByDate) { date in
VStack {
HStack {
Text(date.date).bold()
Spacer()
}
VStack {
ForEach(date.browseItems) { item in
NavigationLink(
destination: BrowseActiveView()
.navigationBarTitle(Text("Browse"), displayMode: .inline)
) {
HStack {
BrowseRowCard(
icon: "flame.fill",
category: "Active",
categoryColor: Color.red,
startTime: item.startTime,
endTime: item.endTime
)
Spacer()
}
}
}
}
.padding()
.background(Color.white)
}
.id(date)
}
}
}
}
.padding()
.background(Color(.systemGroupedBackground))
.environment(\.colorScheme, .light)
.accentColor(.black)
}
}
struct BrowseRowCard: View {
var icon: String
var category: String
var categoryColor: Color
var startTime: String
var endTime: String
var body: some View {
HStack(alignment: .center, spacing: 16) {
Image(systemName: icon)
.font(.title3)
.foregroundColor(.white)
.background(Circle()
.fill(categoryColor)
.frame(width: 38, height: 38)
)
.frame(width: 38, height: 38)
VStack(alignment: .leading, spacing: 3) {
Text(category)
.font(.body)
.fontWeight(.semibold)
Text(startTime + " - " + endTime)
.font(.footnote)
.fontWeight(.semibold)
.foregroundColor(.gray)
}
}
.padding(.vertical, 8)
}
}
struct ScrollableView_Previews: PreviewProvider {
static var previews: some View {
ScrollableView()
.previewDevice(PreviewDevice(rawValue: "iPhone 13"))
}
}
import Foundation
extension BrowseGroupedByDate {
static func all() -> [BrowseGroupedByDate] {
return [
BrowseGroupedByDate(
date: "2022/08/22", // 2022-08-22 00:00:00.000+0900
browseItems: [BrowseItem(startTime: "08:00", endTime: "09:00", mode: "relax", group: "test", team: "test", user: "test"),BrowseItem(startTime: "09:30", endTime: "09:50", mode: "active", group: "test", team: "test", user: "test"),BrowseItem(startTime: "10:00", endTime: "11:00", mode: "relax", group: "test", team: "test", user: "test"),BrowseItem(startTime: "12:00", endTime: "12:50", mode: "active", group: "test", team: "test", user: "test"),BrowseItem(startTime: "13:00", endTime: "13:30", mode: "relax", group: "test", team: "test", user: "test"),BrowseItem(startTime: "15:00", endTime: "18:00", mode: "xxxxx", group: "xxxxx", team: "xxxxx", user: "xxxxx")]
),
BrowseGroupedByDate(
date: "2022/08/21", // 2022-08-22 00:00:00.000+0900
browseItems: [BrowseItem(startTime: "08:00", endTime: "09:00", mode: "relax", group: "test", team: "test", user: "test"),BrowseItem(startTime: "09:30", endTime: "09:50", mode: "active", group: "test", team: "test", user: "test"),BrowseItem(startTime: "10:00", endTime: "11:00", mode: "relax", group: "test", team: "test", user: "test"),BrowseItem(startTime: "12:00", endTime: "12:50", mode: "active", group: "test", team: "test", user: "test")]
),
BrowseGroupedByDate(
date: "2022/08/20", // 2022-08-20 00:00:00.000+0900
browseItems: [BrowseItem(startTime: "08:00", endTime: "09:00", mode: "relax", group: "test", team: "test", user: "test"),BrowseItem(startTime: "09:30", endTime: "09:50", mode: "active", group: "test", team: "test", user: "test"),BrowseItem(startTime: "10:00", endTime: "11:00", mode: "relax", group: "test", team: "test", user: "test")]
)
]
}
}
struct BrowseGroupedByDate: Identifiable, Hashable {
var id = UUID()
var date: String
var browseItems: [BrowseItem]
}
struct BrowseItem: Identifiable, Hashable {
var id = UUID()
var startTime: String
var endTime: String
var mode: String
var group: String
var team: String
var user: String
}

SwiftUI - grid / table with column headings

I'm trying to achieve a design which essentially has the following layout:
So I need column headings for this table or grid view. The second column does not need a heading.
This seems like a job for a LazyVGrid or LazyHGrid, however I can't seem to find a solution which neatly includes headings like this - it all seems a bit hacky.
Wondering if I am missing a better way to achieve this. I could of course try and create VStacks within HStacks, but this just seems like something which should be accomplished more intelligently than this.
You can just pass the headers into the LazyVGrid before you show the content:
struct Item: Identifiable {
let id = UUID()
var item: String
var description: String = "This is the item description"
var quantity: Int = 1
var price: Double = 0
}
struct ContentView: View {
let data = [
Item(item: "Image 1", quantity: 2, price: 1.99),
Item(item: "Image 2", quantity: 1, price: 3.99),
Item(item: "Image 3", quantity: 5, price: 9.99),
]
let columns = [
GridItem(.flexible(), alignment: .topLeading),
GridItem(.flexible(minimum: 150), alignment: .topLeading),
GridItem(.flexible(), alignment: .topLeading),
GridItem(.flexible(), alignment: .topLeading),
]
var body: some View {
LazyVGrid(columns: columns) {
// headers
Group {
Text("Item")
Text("")
Text("Qty")
Text("Price")
}
.font(.headline)
// content
ForEach(data) { item in
Text(item.item)
Text(item.description)
Text("\(item.quantity)")
Text("$\(item.price, specifier: "%.2f")")
}
}
.padding()
}
}

Cell PickerTextField is not tappable

I am trying to show a scrollview in which every cell has 3 picker text field.
However, only the last cell has a working picker text field that is tappable.
When I check BasketListCell preview it is just like I wanted. All the pickers are working well with placeholders. But in scrollview it does not.
I have 3 different listData in BasketList but ShoppingCartView repeats the first one 3 times.
so this is my cell struct Basket that has 3 Int for each picker text field.
struct Basket: Identifiable {
var id: Int {
return elementID
}
var elementID: Int
var image: String
var title: String
var brand: String
var price: String
var selectionImage: String
var discountedPrice: String
var sizeSelectedIndex: Int?
var colorSelectedIndex: Int?
var itemSelectedIndex : Int?
}
That is my demo BasketList with 3 elements.
struct BasketList {
static let listData: [Basket] = [
Basket(elementID: 0, image: "Tee", title: "3-Stripes Shirt", brand: "Adidas Original", price: "$79.40", selectionImage: "checkmark", discountedPrice: "$48.99", sizeSelectedIndex: 0, colorSelectedIndex: 0, itemSelectedIndex: 0),
Basket(elementID: 0, image: "Blouse", title: "Tuxedo Blouse", brand: "Lost Ink", price: "$50.00", selectionImage: "checkmark", discountedPrice: "$34.90", sizeSelectedIndex: 0, colorSelectedIndex: 0, itemSelectedIndex: 0),
Basket(elementID: 0, image: "Tee", title: "Linear Near Tee", brand: "Converse", price: "$28.50", selectionImage: "checkmark", discountedPrice: "$19.99", sizeSelectedIndex: 0, colorSelectedIndex: 0, itemSelectedIndex: 0)
]
}
That is my BasketCell
struct BasketListCell: View {
#State var isSelected: Bool = false
#State private var itemSelectedIndex : Int?
#State private var sizeSelectedIndex : Int?
#State private var colorSelectedIndex: Int?
var colors = ["Black", "Green", "Red", "Blue"]
var sizes = ["XS", "S", "M", "L", "XL", "XXL"]
var items = ["1", "2", "3", "4", "5", "6"]
var item: Basket
var body: some View {
HStack(spacing: 20) {
ZStack {
PickerTextField(data: sizes, placeHolder: "Size", lastSelectedIndex: self.$sizeSelectedIndex)
.overlay(
Image(systemName: "chevron.down")
)
}
.frame(width: UIScreen.main.bounds.width / (375/100), height: 44, alignment: .center)
ZStack{
PickerTextField(data: colors, placeHolder: "Color", lastSelectedIndex: self.$colorSelectedIndex)
.overlay(
Image(systemName: "chevron.down")
)
}
.frame(width: UIScreen.main.bounds.width / (375/100), height: 44, alignment: .center)
ZStack {
PickerTextField(data: items, placeHolder: "Item", lastSelectedIndex: self.$itemSelectedIndex)
.overlay(
Image(systemName: "chevron.down")
)
}
.frame(width: UIScreen.main.bounds.width / (375/100), height: 44, alignment: .center)
}
}
and finally, this is my ShoppingCartView
struct ShoppingCartView: View {
#State var selectedProduct = Basket(elementID: 0, image: "", title: "", brand: "", price: "", selectionImage: "", discountedPrice: "", sizeSelectedIndex: 0, colorSelectedIndex: 0, itemSelectedIndex: 0)
#State var shown: Bool = false
var body: some View {
ZStack {
Color(.white)
.edgesIgnoringSafeArea(.all)
VStack {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 20) {
ForEach(BasketList.listData) { item in
BasketListCell(item: item)
.onTapGesture {
self.shown = true
self.selectedProduct = item
}
}
}
}
You can understand clearly my problem if you check those images better.
This is BasketListCell preview
That is my ShoppingCartView
elementID attribute of your Model class Basket needs to be unique. You currently have 3 Basket objects all with duplicate identifier, causing swiftUI to read first object every time. Changing it to some unique values should fix the problem.
Current-:
struct BasketList {
static let listData: [Basket] = [
Basket(elementID: 0, image: "Tee", title: "3-Stripes Shirt", brand: "Adidas Original", price: "$79.40", selectionImage: "checkmark", discountedPrice: "$48.99", sizeSelectedIndex: 0, colorSelectedIndex: 0, itemSelectedIndex: 0),
Basket(elementID: 0, image: "Blouse", title: "Tuxedo Blouse", brand: "Lost Ink", price: "$50.00", selectionImage: "checkmark", discountedPrice: "$34.90", sizeSelectedIndex: 0, colorSelectedIndex: 0, itemSelectedIndex: 0),
Basket(elementID: 0, image: "Tee", title: "Linear Near Tee", brand: "Converse", price: "$28.50", selectionImage: "checkmark", discountedPrice: "$19.99", sizeSelectedIndex: 0, colorSelectedIndex: 0, itemSelectedIndex: 0)
]
}
Fix-:
struct BasketList {
static let listData: [Basket] = [
Basket(elementID: 1, image: "Tee", title: "3-Stripes Shirt", brand: "Adidas Original", price: "$79.40", selectionImage: "checkmark", discountedPrice: "$48.99", sizeSelectedIndex: 0, colorSelectedIndex: 0, itemSelectedIndex: 0),
Basket(elementID: 2, image: "Blouse", title: "Tuxedo Blouse", brand: "Lost Ink", price: "$50.00", selectionImage: "checkmark", discountedPrice: "$34.90", sizeSelectedIndex: 0, colorSelectedIndex: 0, itemSelectedIndex: 0),
Basket(elementID: 3, image: "Tee", title: "Linear Near Tee", brand: "Converse", price: "$28.50", selectionImage: "checkmark", discountedPrice: "$19.99", sizeSelectedIndex: 0, colorSelectedIndex: 0, itemSelectedIndex: 0)
]
}

SwiftUI Navigation with complex View Model (Defer creation until actually needed)

this SwiftUI app uses a NavigationView with one dedicated view model for each of the two involved views.
What bothers me is the creation of the second view model (DetailsViewModel) for each of the shown links regardless of whether that link is ever going to be pressed or not. You can see this in console log.
This is a minimal example, imagine the data for the second view model coming from different entities, even different data stores and the func makeDetailsViewModel(id:) to be expensive in its execution.
I would like to defer the creation of the DetailsViewModel until the user has clicked one of the links so that only that one needs to be created instead of the potential dozens or or hundreds more others. It seams reasonable to me and straight forward in UIKit by injecting the view model in prepare(for segue:) but I don’t know how to achieve something like this using SwiftUI.
Apple’s tutorial Building Lists and Navigation and other tutorials I found use the same view model or none at all (e. g. Text("Second view")) for the second view.
How can this be done or is there a completely different way in SwiftUI to go about this? I may be mentally stuck in UIKit.
Thank you.
import SwiftUI
var people: [Person] = [
Person(id: 1, name: "Alice", age: 13, country: "USA"),
Person(id: 2, name: "Brad", age: 29, country: "Canada"),
Person(id: 3, name: "Chad", age: 29, country: "Canada"),
Person(id: 4, name: "Dorothy", age: 62, country: "Ireland"),
Person(id: 5, name: "Elizabeth", age: 40, country: "Sweden"),
Person(id: 6, name: "Francois", age: 21, country: "France"),
Person(id: 7, name: "Gary", age: 36, country: "Singapore"),
Person(id: 8, name: "Hans", age: 28, country: "Germany"),
Person(id: 9, name: "Ivan", age: 70, country: "Russia"),
Person(id: 10, name: "Jaime", age: 45, country: "Spain"),
]
var nameList: [ListEntryViewModel] = {
people.map { person in
return ListEntryViewModel(id: person.id, name: person.name)
}
}()
func makeDetailsViewModel(id: Int) -> DetailsViewModel {
print("Creating view model for DetailView")
let person = people.first(where: { $0.id == id})!
return DetailsViewModel(name: person.name, age: person.age, country: person.country)
}
// MARK: - Model
struct Person {
var id: Int
var name: String
var age: Int
var country: String
}
// MARK: - View Models
struct ListEntryViewModel: Identifiable {
var id: Int
var name: String
}
struct DetailsViewModel {
var name: String
var age: Int
var country: String
}
// MARK: - Views
struct DetailView: View {
var detailsViewModel: DetailsViewModel
var body: some View {
VStack {
Text(detailsViewModel.name)
Text("\(detailsViewModel.age) years")
Text("from \(detailsViewModel.country)")
}
}
}
struct ContentView: View {
var people: [ListEntryViewModel] = nameList
var body: some View {
NavigationView {
List(people) { person in
NavigationLink(destination: DetailView(detailsViewModel: makeDetailsViewModel(id: person.id))) {
Text(person.name)
}
}
.navigationTitle("People")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I assume you wanted DeferView (from https://stackoverflow.com/a/61242931/12299030), like
List(people) { person in
NavigationLink(destination: DeferView {
DetailView(detailsViewModel: makeDetailsViewModel(id: person.id))
}) {
Text(person.name)
}
}

problems with #binding between structs

in my code I have:
struct Specialty {
let type:String
let color:Color
let image:Image
}
// MARK: Search
struct Search: View {
#State var show = false
#State var txt = ""
#State var index = 1
let specialtyList = [
Specialty(type: "Cardiologia", color: Color.blue, image: Image("google1") ),
Specialty(type: "Clínica Médica", color: Color.pink, image: Image("google1")),
Specialty(type: "Dermatologia", color: Color("Color"), image: Image("google1")),
Specialty(type: "Ginecologia e Obstetrícia", color: Color.pink, image: Image("google1")),
Specialty(type: "Medicina do Trabalho", color: Color.red, image: Image("google1")),
Specialty(type: "Oftalmologia", color: Color("Color"), image: Image("google1")),
Specialty(type: "Ortopedia", color: Color.pink, image: Image("google1")),
Specialty(type: "Otorrinolaringologia", color: Color.blue, image: Image("google1")),
Specialty(type: "Pediatria", color: Color.red, image: Image("google1")),
Specialty(type: "Psiquiatria", color: Color("Color"), image: Image("google1")),
Specialty(type: "Radiologia", color: Color("Color"), image: Image("google1"))
]
}
ignore the same google1 images, I'm just trying to get the code to work first.
Then, in the View of Search, I have:
ForEach(specialtyList, id: \.type){ Specialty in
NavigationLink (destination: SearchBar()){
VStack(spacing: 18) {
HStack{
Text(Specialty.type).foregroundColor(.white)
Specialty.image
.renderingMode(.original)
.resizable()
.frame(width: 35, height: 35)
}
}
}
}
to show the information in 'let specialtyList' as a scrollView
As each world displayed works as a button, Im trying that, when I go to the destination (which is SearchBar() in this case), I want to have different information shown depending on the NavigationLink text that is pressed.
How could I do it by using the order in the list 'specialtyList' and how could a simply print, in the destination, the same name of the NavigationLink text that was pressed?
I assume this is it
Note: try to avoid same naming for type (like, Specialty, capitalised) and instance/value (like, specialty, lowercased), otherwise it might confuse and compiler and you.
ForEach(specialtyList, id: \.type){ specialty in // << named correctly
NavigationLink (destination: SearchBar(item: specialty)){ // << inject here
VStack(spacing: 18) {
HStack{
Text(specialty.type).foregroundColor(.white)
specialty.image
.renderingMode(.original)
.resizable()
.frame(width: 35, height: 35)
}
}
}
}
and now in view
struct SearchBar: View {
let item: Specialty
var body: some View {
Text(item.type)
// .. other code
}
}