How to setup NavigationLink in SwiftUI sheet to redirect to new view - swift

I am attempting to build a multifaceted openweathermap app. My app is designed to prompt the user to input a city name on a WelcomeView, in order to get weather data for that city. After clicking search, the user is redirected to a sheet with destination: DetailView, which displays weather details about that requested city. My goal is to disable dismissal of the sheet in WelcomeView and instead add a navigationlink to the sheet that redirects to the ContentView. The ContentView in turn is set up to display a list of the user's recent searches (also in the form of navigation links).
My issues are the following:
The navigationLink in the WelcomeView sheet does not work. It appears to be disabled. How can I configure the navigationLink to segue to destination: ContentView() ?
After clicking the navigationLink and redirecting to ContentView, I want to ensure that the city name entered in the WelcomeView textfield is rendered as a list item in the ContentView. For that to work, would it be necessary to set up an action in NavigationLink to call viewModel.fetchWeather(for: cityName)?
Here is my code:
WelcomeView
struct WelcomeView: View {
#StateObject var viewModel = WeatherViewModel()
#State private var cityName = ""
#State private var showingDetail: Bool = false
#State private var linkActive: Bool = true
#State private var acceptedTerms = false
var body: some View {
Section {
HStack {
TextField("Search Weather by City", text: $cityName)
.padding()
.overlay(RoundedRectangle(cornerRadius: 10.0).strokeBorder(Color.gray, style: StrokeStyle(lineWidth: 1.0)))
.padding()
Spacer()
Button(action: {
viewModel.fetchWeather(for: cityName)
cityName = ""
self.showingDetail.toggle()
}) {
HStack {
Image(systemName: "plus")
.font(.title)
}
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}
.sheet(isPresented: $showingDetail) {
VStack {
NavigationLink(destination: ContentView()){
Text("Return to Search")
}
ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
if (city == viewModel.cityNameList.count-1) {
DetailView(detail: viewModel.cityNameList[city])
}
}.interactiveDismissDisabled(!acceptedTerms)
}
}
}.padding()
}
}
}
struct WelcomeView_Previews: PreviewProvider {
static var previews: some View {
WelcomeView()
}
}
ContentView
let coloredToolbarAppearance = UIToolbarAppearance()
struct ContentView: View {
// Whenever something in the viewmodel changes, the content view will know to update the UI related elements
#StateObject var viewModel = WeatherViewModel()
#State private var cityName = ""
#State var showingDetail = false
init() {
// toolbar attributes
coloredToolbarAppearance.configureWithOpaqueBackground()
coloredToolbarAppearance.backgroundColor = .systemGray5
UIToolbar.appearance().standardAppearance = coloredToolbarAppearance
UIToolbar.appearance().scrollEdgeAppearance = coloredToolbarAppearance
}
var body: some View {
NavigationView {
VStack() {
List () {
ForEach(viewModel.cityNameList) { city in
NavigationLink(destination: DetailView(detail: city)) {
HStack {
Text(city.name).font(.system(size: 32))
Spacer()
Text("\(city.main.temp, specifier: "%.0f")°").font(.system(size: 32))
}
}
}.onDelete { index in
self.viewModel.cityNameList.remove(atOffsets: index)
}
}.onAppear() {
viewModel.fetchWeather(for: cityName)
}
}.navigationTitle("Weather")
.toolbar {
ToolbarItem(placement: .bottomBar) {
HStack {
TextField("Enter City Name", text: $cityName)
.frame(minWidth: 100, idealWidth: 150, maxWidth: 240, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
Spacer()
Button(action: {
viewModel.fetchWeather(for: cityName)
cityName = ""
self.showingDetail.toggle()
}) {
HStack {
Image(systemName: "plus")
.font(.title)
}
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}.sheet(isPresented: $showingDetail) {
ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
if (city == viewModel.cityNameList.count-1) {
DetailView(detail: viewModel.cityNameList[city])
}
}
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
DetailView
struct DetailView: View {
var detail: WeatherModel
var body: some View {
VStack(spacing: 20) {
Text(detail.name)
.font(.system(size: 32))
Text("\(detail.main.temp, specifier: "%.0f")°")
.font(.system(size: 44))
Text(detail.firstWeatherInfo())
.font(.system(size: 24))
}
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(detail: WeatherModel.init())
}
}
ViewModel
class WeatherViewModel: ObservableObject {
#Published var cityNameList = [WeatherModel]()
func fetchWeather(for cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=imperial&appid=<MyAPIKey>") else { return }
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else { return }
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.cityNameList.append(model)
}
}
catch {
print(error) // <-- you HAVE TO deal with errors here
}
}
task.resume()
}
}
Model
struct WeatherModel: Identifiable, Codable {
let id = UUID()
var name: String = ""
var main: CurrentWeather = CurrentWeather()
var weather: [WeatherInfo] = []
func firstWeatherInfo() -> String {
return weather.count > 0 ? weather[0].description : ""
}
}
struct CurrentWeather: Codable {
var temp: Double = 0.0
}
struct WeatherInfo: Codable {
var description: String = ""
}
DemoApp
#main
struct SwftUIMVVMWeatherDemoApp: App {
var body: some Scene {
WindowGroup {
// ContentView()
WelcomeView()
}
}
}

Related

Dynamic list from TextField's data SwiftUI

I just started to learn Swift programing language and have a question.
I'm trying to create a simple one-page application where you can add movies to a favorite list. Movies must have 2 properties: title (string, mandatory) and year (integer, mandatory). But I have a problem, I don't know how to put it in one row.
And also, how to ignore duplicate movies?
import SwiftUI
struct Movie: Identifiable {
let id = UUID()
var title: String
}
class Model: ObservableObject {
#Published var movies: [Movie] = []
}
struct DynamicList: View {
#StateObject var model = Model()
#State var text = ""
#State var year = ""
var body: some View {
VStack {
Section() {
TextField("Title", text: $text)
.padding()
.border(.gray)
TextField("Year", text: $year)
.padding()
.border(.gray)
.keyboardType(.numberPad)
Button(action: {
self.addToList()
}, label: {
Text("Add")
.frame(width: 80, height: 40, alignment: .center)
.background(Color.blue)
.cornerRadius(8)
.foregroundColor(.white)
})
.padding()
}
List {
ForEach(model.movies) { movie in
MovieRow(title: movie.title)
}
}
}
.padding()
}
func addToList() {
guard !text.trimmingCharacters(in: .whitespaces).isEmpty else {
return
}
guard !year.trimmingCharacters(in: .whitespaces).isEmpty else {
return
}
let newMovie = Movie(title: text)
model.movies.append(newMovie)
text = ""
let newYear = Movie(title: year)
model.movies.append(newYear)
year = ""
}
}
struct MovieRow: View {
let title: String
var body: some View {
Label (
title: { Text(title)},
icon: { Image(systemName: "film") }
)
}
}
struct DynamicList_Previews: PreviewProvider {
static var previews: some View {
DynamicList()
}
}
Here is the solution. It will show the data in one row and also how to ignore duplicate movies to show into the list. Check the below code:
import SwiftUI
struct Movie: Identifiable {
var id = UUID()
let title: String
let year: String
}
class MoviesViewModel: ObservableObject {
#Published var movies: [Movie] = []
}
struct ContentView: View {
#State var boolValue = false
#StateObject var viewModel = MoviesViewModel()
#State var text = ""
#State var year = ""
var body: some View {
VStack {
Section() {
TextField("Title", text: $text)
.padding()
.border(.gray)
TextField("Year", text: $year)
.padding()
.border(.gray)
.keyboardType(.numberPad)
Button(action: {
self.addToList()
}, label: {
Text("Add")
.frame(width: 80, height: 40, alignment: .center)
.background(Color.blue)
.cornerRadius(8)
.foregroundColor(.white)
})
.padding()
}
// Show the data in list form
List {
ForEach(viewModel.movies) { movie in
MovieRow(title: movie.title, year: movie.year)
}
}
}
.padding()
}
func addToList() {
guard !text.trimmingCharacters(in: .whitespaces).isEmpty else {
return
}
guard !year.trimmingCharacters(in: .whitespaces).isEmpty else {
return
}
// Condition to check whether the data is already exit or not
boolValue = false
let newMovie = Movie(title: text, year: year)
for movie in viewModel.movies{
if ((movie.title.contains(text)) && (movie.year.contains(year))){
boolValue = true
}
}
// check if boolValue is false so the data will store into the array.
if boolValue == false{
viewModel.movies.append(newMovie)
text = ""
year = ""
}
}
}
struct MovieRow: View {
let title: String
let year: String
var body: some View {
// Show the data insert into the textfield
HStack{
Label (
title: { Text(title)},
icon: { Image(systemName: "film") }
)
Spacer()
Label (
title: { Text(year)},
icon: { Image(systemName: "calendar") }
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Maybe someone will need a similar solution, here is my result:
import SwiftUI
struct Movie: Identifiable {
let id = UUID()
var title: String
var year: String
}
class Model: ObservableObject {
#Published var movies: [Movie] = []
}
struct DynamicList: View {
#StateObject var model = Model()
#State var text = ""
#State var year = ""
var body: some View {
VStack {
Section() {
TextField("Title", text: $text)
.padding()
.border(.gray)
TextField("Year", text: $year)
.padding()
.border(.gray)
.keyboardType(.numberPad)
Button(action: {
self.addToList()
}, label: {
Text("Add")
.frame(width: 80, height: 40, alignment: .center)
.background(Color.blue)
.cornerRadius(8)
.foregroundColor(.white)
})
.padding()
}
List {
ForEach(model.movies) { movie in
MovieRow(title: movie.title, year: movie.year)
}
}
}
.padding()
}
func addToList() {
guard !text.trimmingCharacters(in: .whitespaces).isEmpty else {
return
}
guard !year.trimmingCharacters(in: .whitespaces).isEmpty else {
return
}
let newMovie = Movie(title: text, year: year)
model.movies.append(newMovie)
text = ""
year = ""
}
}
struct MovieRow: View {
let title: String
let year: String
var body: some View {
Label (
title: { Text(title + " " + year)},
icon: { Image(systemName: "film") }
)
}
}
struct DynamicList_Previews: PreviewProvider {
static var previews: some View {
DynamicList()
}
}

How to append to a struct from another file?

I am trying to append to a struct from another file. I can do so, and it works in one file, but when I try to connect the second, it doesn't work.
I am trying to implement a checkout feature, and right now, I need to at least be able to append items to the cart.
currentOrderLogic.swift
struct Cart: Hashable, Identifiable {
let id = UUID()
var product_name: String
var product_cost: String
}
public struct CurrentOrder: View {
#State var items = [Cart]()
func addObject(product_name: String, product_cost: String) {
items.append(Cart(product_name: product_name, product_cost: product_cost))
}
public var body: some View {
ScrollView(.vertical) {
VStack(alignment: .center) {
Text("Current Order".uppercased()).font(.system(size: 30))
.frame(height:50)
ForEach(items, id: \.self) { item in
HStack{
Text(item.product_name)
Text(item.product_cost)
}
}
Spacer()
Button("Charge $0.00") {
addObject(product_name: "Aooke", product_cost: "6")
}
}
}
}
}
When I press the button, it is added and shown in the list.
Now, I am trying to have items that they can click, and when it is clicked, the item is added to the current order.
homeMenu.swift
struct homeMenuObject: View {
#State var posts: [Post] = []
let date = Date()
var body: some View {
VStack {
HStack {
Text(date, style: .date)
Text(date, style: .time)
}
WrappingHStack(posts, id: \.self){ item in
VStack {
HStack {
Image("Logo").resizable().frame(width: 110, height: 55)
}
HStack {
Text(item.title)
}
HStack {
Text((item.body).uppercased())
.font(.headline)
}
}.frame(width: 110, height: 140).background(Color.white).onTapGesture {
CurrentOrder().addObject(product_name: "Aooke", product_cost: "6")
}
}.onAppear {
Api().getPosts { (posts) in
self.posts = posts
}
}
Spacer()
}
}
}
But when CurrentOrder().addObject(product_name: "Aooke", product_cost: "6") is called, nothing happens. How can I fix this?
Using ObservedObjects, I figured this out MYSELF.
currentOrderLogic.swift
public struct CurrentOrder: View {
#ObservedObject var cartSystem : CartSystem
public var body: some View {
ScrollView(.vertical) {
VStack(alignment: .center) {
Text("Current Order".uppercased()).font(.system(size: 30))
.frame(height:50)
ForEach(cartSystem.items, id: \.self) { item in
HStack{
Text(item.product_name)
Text(item.product_cost)
}
}
Spacer()
Button("Charge $0.00") {
cartSystem.addObject(product_name: "Aooke", product_cost: "6")
}
}
}
}
}
homeMenu.swift
struct homeMenuObject: View {
#State var posts: [Post] = []
#ObservedObject var cartSystem : CartSystem
let date = Date()
var body: some View {
VStack {
HStack {
Text(date, style: .date)
Text(date, style: .time)
}
WrappingHStack(posts, id: \.self){ item in
VStack {
HStack {
Image("Logo").resizable().frame(width: 110, height: 55)
}
HStack {
Text(item.title)
}
HStack {
Text((item.body).uppercased())
.font(.headline)
}
}.frame(width: 110, height: 140).background(Color.white).onTapGesture {
cartSystem.addObject(product_name: "Aooke", product_cost: "6")
}
}.onAppear {
Api().getPosts { (posts) in
self.posts = posts
}
}
Spacer()
}
}
}
Then, in your main ContentView, I put this outside of the ContentView Structure,
class CartSystem: ObservableObject {
#Published var items = [Cart]()
func addObject(product_name: String, product_cost: String) {
self.items.append(Cart(product_name: product_name, product_cost: product_cost))
}
struct Cart: Hashable, Identifiable {
let id = UUID()
var product_name: String
var product_cost: String
}
}
Then I put this inside the ContentView struct
#StateObject var cartSystem : CartSystem = CartSystem()
Then, when I call the CurrentOrder struct and the HomeMenu struct, you pass this cartSystem variable in. This makes all of the CartSystem's the same, so you are not creating new instances of them and makes them connected.
Now i need to make the homeMenu objects pass their respective data into the order

SwiftUI - Display selected value with Single Selected

I want when I finish selecting the language and click the Save button it will return the ContentView page and display the language I have selected. And when I click again, it has to checkmark the language I selected before.
I have successfully displayed the data, but I don't know how to save it when I click the Save button
Here is all my code currently
ContentView
struct ContentView: View {
var body: some View {
NavigationView {
HStack {
NavigationLink(destination:LanguageView() ) {
Text("Language")
Spacer()
Text("I want to show the language here ")
}
}
}
}
}
LanguageView
struct LanguageView: View {
var body: some View {
VStack {
CustomLanguageView()
Button(action: {
})
{
Text("Save")
.foregroundColor(.black)
}
.padding()
Spacer()
}
}
}
struct CustomLanguageView: View {
var language = ["US", "English", "Mexico", "Canada"]
#State var selectedLanguage: String? = nil
var body: some View {
LazyVStack {
ForEach(language, id: \.self) { item in
SelectionCell(language: item, selectedLanguage: self.$selectedLanguage)
.padding(.trailing,40)
Rectangle().fill(Color.gray)
.frame( height: 1,alignment: .bottom)
}
.frame(height:15)
}
}
}
struct SelectionCell: View {
let language: String
#Binding var selectedLanguage: String?
var body: some View {
HStack {
Text(language)
Spacer()
if language == selectedLanguage {
Image(systemName: "checkmark")
.resizable()
.frame(width:20, height: 15)
}
}
.onTapGesture {
self.selectedLanguage = self.language
}
}
}
There are multiple ways to "Save" something but if you are just trying to get it back to the other view you could do something like this that I quickly setup.
struct ContentView: View {
#State var language: String? = ""
var body: some View {
NavigationView {
HStack {
NavigationLink(destination:LanguageView(language: $language)) {
Text("Language")
.padding()
Spacer()
Text(language!)
.padding()
}
}
}
}
}
struct LanguageView: View {
#Binding var language: String?
#State var selectedLanguage: String? = ""
var body: some View {
VStack {
CustomLanguageView(selectedLanguage: $selectedLanguage)
Button(action: {
language = selectedLanguage
})
{
Text("Save")
.foregroundColor(.black)
}
.padding()
Spacer()
}
}
}
struct CustomLanguageView: View {
var language = ["US", "English", "Mexico", "Canada"]
#Binding var selectedLanguage: String?
var body: some View {
LazyVStack {
ForEach(language, id: \.self) { item in
SelectionCell(language: item, selectedLanguage: self.$selectedLanguage)
.padding(.trailing,40)
Rectangle().fill(Color.gray)
.frame( height: 1,alignment: .bottom)
}
.frame(height:15)
}
}
}
struct SelectionCell: View {
let language: String
#Binding var selectedLanguage: String?
var body: some View {
HStack {
Text(language)
Spacer()
if language == selectedLanguage {
Image(systemName: "checkmark")
.resizable()
.frame(width:20, height: 15)
}
}
.onTapGesture {
self.selectedLanguage = self.language
}
}
}
Or if you are actually trying to save it to the device for later use you could use
UserDefaults.standard.setValue(selectedLanguage, forKey: "language")
Then to Retrieve it later do
UserDefaults.standard.value(forKey: "language") as! String

SwiftUI - Save status when language is selected

I have successfully displayed the language in the UI, but I have a problem: when I click the "Save" button it still doesn't save the language I selected.
I want when I have selected the language and clicked the Save Button , it will return to the previous page and when I click it again it will show the language I selected before.
Above data is simulation only, I mainly focus on its features
All my current code
struct ContentView: View {
var language: String? = ""
var body: some View {
NavigationView {
HStack {
NavigationLink(destination:LanguageView(language: language)) {
Text("Language")
.padding()
Spacer()
Text(language!)
.padding()
}
}
}
}
}
struct LanguageView: View {
#Environment(\.presentationMode) var pres
#State var language: String?
#State var selectedLanguage: String? = ""
var body: some View {
VStack {
CustomLanguageView(selectedLanguage: $language)
Button(action: {
language = selectedLanguage
pres.wrappedValue.dismiss()
})
{
Text("Save")
.foregroundColor(.black)
}
.padding()
Spacer()
}
}
}
struct CustomLanguageView: View {
var language = ["US", "English", "Mexico", "Canada"]
#Binding var selectedLanguage: String?
var body: some View {
LazyVStack {
ForEach(language, id: \.self) { item in
SelectionCell(language: item, selectedLanguage: self.$selectedLanguage)
.padding(.trailing,40)
Rectangle().fill(Color.gray)
.frame( height: 1,alignment: .bottom)
}
.frame(height:15)
}
}
}
struct SelectionCell: View {
let language: String
#Binding var selectedLanguage: String?
var body: some View {
HStack {
Text(language)
Spacer()
if language == selectedLanguage {
Image(systemName: "checkmark")
.resizable()
.frame(width:20, height: 15)
}
}
.onTapGesture {
self.selectedLanguage = self.language
}
}
}
Edit to my previous answer, since something is blocking my edit to my previous answer, this one shows all the code I used to make it works well for me:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var language: String? = ""
var body: some View {
NavigationView {
HStack {
NavigationLink(destination: LanguageView(language: $language)) {
Text("Language").padding()
Spacer()
Text(language!).padding()
}
}
}
}
}
struct LanguageView: View {
#Environment(\.presentationMode) var pres
#Binding var language: String?
var body: some View {
VStack {
CustomLanguageView(selectedLanguage: $language) // <--- here
Button(action: { pres.wrappedValue.dismiss() }) { // <--- here
Text("Save").foregroundColor(.black)
}.padding()
Spacer()
}
}
}
struct CustomLanguageView: View {
var language = ["US", "English", "Mexico", "Canada"]
#Binding var selectedLanguage: String?
var body: some View {
LazyVStack {
ForEach(language, id: \.self) { item in
SelectionCell(language: item, selectedLanguage: self.$selectedLanguage).padding(.trailing,40)
Rectangle().fill(Color.gray).frame( height: 1,alignment: .bottom)
}.frame(height:15)
}
}
}
struct SelectionCell: View {
let language: String
#Binding var selectedLanguage: String?
var body: some View {
HStack {
Text(language)
Spacer()
if language == selectedLanguage {
Image(systemName: "checkmark").resizable().frame(width:20, height: 15)
}
}
.onTapGesture {
self.selectedLanguage = self.language
}
}
}
you could use #State var language throughout and use dismiss, such as this,
to achieve what you want:
struct LanguageView: View {
#Environment(\.dismiss) var dismiss // <--- here
#Binding var language: String?
var body: some View {
VStack {
CustomLanguageView(selectedLanguage: $language) // <--- here
Button(action: {
dismiss() // <--- here
})
{
Text("Save").foregroundColor(.black)
}
.padding()
Spacer()
}
}
}

Background of button not conditionally rendering

Essentially I have a button when pressed I want the background to become a different color. In order to do this I have an object that I alter, I have printed out the value of the Bool value in the object and see its changing but the color of the button is not changing.
Object With Bool:
class dummyObject: Identifiable, ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var id = UUID()
var isSelected: Bool {
willSet {
objectWillChange.send()
}
}
init(isSelected:Bool) {
self.isSelected = isSelected
}
}
View:
struct SelectionView: View {
var objs: [dummyObject] = [
dummyObject.init(isSelected: false)
]
var body: some View {
HStack{
ForEach(objs) { obj in
Button(action: {
obj.isSelected.toggle()
print("\(obj.isSelected)")
}) {
VStack {
Text("Test")
.foregroundColor(obj.isSelected ? Color.white : Color.gray)
.font(.caption)
}
}.frame(width:55,height: 55)
.padding()
.background(obj.isSelected ? Color.red : Color.white)
.padding(.horizontal, 3)
.clipShape(Circle()).shadow(radius: 6)
}
}.frame(minWidth: 0, maxWidth: .infinity)
.padding()
}
}
Extract your Button into other view, where obj is #ObservedObject and everything will work:
import SwiftUI
import Combine
class dummyObject: Identifiable, ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var id = UUID()
var isSelected: Bool {
willSet {
objectWillChange.send()
}
}
init(isSelected:Bool) {
self.isSelected = isSelected
}
}
struct SelectionView: View {
var objs: [dummyObject] = [dummyObject.init(isSelected: false)]
var body: some View {
HStack{
ForEach(objs) { obj in
ObjectButton(obj: obj)
}
}
}
}
struct ObjectButton: View {
#ObservedObject var obj: dummyObject
var body: some View {
Button(action: {
self.obj.isSelected.toggle()
print("\(self.obj.isSelected)")
}) {
VStack {
Text("Test")
.foregroundColor(obj.isSelected ? Color.white : Color.gray)
.font(.caption)
}
}.frame(width:55,height: 55)
.padding()
.background(obj.isSelected ? Color.red : Color.white)
.padding(.horizontal, 3)
.clipShape(Circle()).shadow(radius: 6)
}
}
struct SelectionView_Previews: PreviewProvider {
static var previews: some View {
SelectionView()
}
}
Here is modified your snapshot of code that works. Tested with Xcode 11.2 / iOS 13.2.
The main idea is made a model as value-type, so modifications of properties modify model itself, and introducing #State for view would refresh on changes.
struct dummyObject: Identifiable, Hashable {
var id = UUID()
var isSelected: Bool
}
struct SelectionView: View {
#State var objs: [dummyObject] = [
dummyObject(isSelected: false)
]
var body: some View {
HStack{
ForEach(Array(objs.enumerated()), id: \.element) { (i, _) in
Button(action: {
self.objs[i].isSelected.toggle()
print("\(self.objs[i].isSelected)")
}) {
VStack {
Text("Test")
.foregroundColor(self.objs[i].isSelected ? Color.white : Color.gray)
.font(.caption)
}
}.frame(width:55,height: 55)
.padding()
.background(self.objs[i].isSelected ? Color.red : Color.white)
.padding(.horizontal, 3)
.clipShape(Circle()).shadow(radius: 6)
}
}.frame(minWidth: 0, maxWidth: .infinity)
.padding()
}
}