View not updating when accessing ObservableObject in Else block - ios

My detail View does not update when I change its #Binding value.
struct ContentView: View {
class ViewModel: ObservableObject {
#Published var imageSize: ImageSize?
#Published var anotherValue: Int? = 5
var cancellable: AnyCancellable?
init() {
cancellable = $imageSize.sink(receiveValue: { imageSize in
print("🚨 \(imageSize?.rawValue ?? "Unknown")")
})
}
}
#ObservedObject private var viewModel = ViewModel()
var body: some View {
NavigationView {
List {
if viewModel.anotherValue == nil {
Text("Another value is nil")
} else {
NavigationLink(destination: SelectImageSizeView(selectedImageSize: $viewModel.imageSize)
.navigationBarTitle("ImageSize", displayMode: .inline)) {
Text("ImageSize: \(viewModel.imageSize?.rawValue ?? "Not Set")")
}
}
}
}
}
}
struct SelectImageSizeView: View {
#Binding var selectedImageSize: ImageSize?
var body: some View {
List{
ForEach(ImageSize.allCases, id: \.self) { imageSize in
HStack {
Button {
withAnimation {
self.selectedImageSize = imageSize
}
} label: {
HStack {
Text(imageSize.rawValue)
Image(systemName: imageSize == selectedImageSize ? "checkmark.circle.fill" : "circle")
}
.font(.title)
}
}
}
}
}
}
public enum ImageSize: String, Codable, CaseIterable {
case small = "Small"
case medium = "Medium"
case large = "Large"
}
It does work when I move the NavigationLink to out of the if block:
NavigationView {
List {
if viewModel.anotherValue == nil {
Text("Another value is nil")
}
NavigationLink(destination: SelectImageSizeView(selectedImageSize: $viewModel.imageSize)
.navigationBarTitle("ImageSize", displayMode: .inline)) {
Text("ImageSize: \(viewModel.imageSize?.rawValue ?? "Not Set")")
}
}
}
Am I missing something here?

Hm. I don't know why that's the case. But you could instead pass over your whole ViewModel?
You would have to put it outside your ContentView. I have made it fileprivate if you don't want to access it from other files.
Works for me.
fileprivate class ViewModel: ObservableObject {
#Published var imageSize: ImageSize?
#Published var anotherValue: Int? = 5
var cancellable: AnyCancellable?
init() {
cancellable = $imageSize.sink(receiveValue: { imageSize in
print("🚨 \(imageSize?.rawValue ?? "Unknown")")
})
}
}
struct ContentView: View {
#StateObject private var viewModel = ViewModel()
var body: some View {
NavigationView {
List {
if viewModel.anotherValue == nil {
Text("Another value is nil")
} else {
NavigationLink(destination: SelectImageSizeView(viewModel: viewModel)
.navigationBarTitle("ImageSize", displayMode: .inline)) {
Text("ImageSize: \(viewModel.imageSize?.rawValue ?? "Not Set")")
}
}
}
}
}
}
struct SelectImageSizeView: View {
#ObservedObject fileprivate var viewModel: ViewModel
var body: some View {
List{
ForEach(ImageSize.allCases, id: \.self) { imageSize in
HStack {
Button {
withAnimation {
viewModel.imageSize = imageSize
}
} label: {
HStack {
Text(imageSize.rawValue)
Image(systemName: imageSize == viewModel.imageSize ? "checkmark.circle.fill" : "circle")
}
.font(.title)
}
}
}
}
}
}
public enum ImageSize: String, Codable, CaseIterable {
case small = "Small"
case medium = "Medium"
case large = "Large"
}

It is very silly but there is bug in simulator. It is working with device.

Related

Swiftui view doesn't refresh when navigated to from a different view

I have, what is probably, a beginner question here. I'm hoping there is something simple I'm missing or I have done wrong.
I essentially have a view which holds a struct containing an array of id strings. I then have a #FirestoreQuery which accesses a collection which holds objects with these id's. My view then displays a list with two sections. One for the id's in the original struct, and one for the remaining ones in the collection which don't appear in the array.
Each listitem is a separate view which displays the details of that item and also includes a button. When this button is pressed it adds/removes that object from the parent list and the view should update to show that object in the opposite section of the list from before.
My issue is that this works fine in the 'preview' in xcode when I look at this view on it's own. However if I run the app in the simulator, or even preview a parent view and navigate to this one, the refreshing of the view doesn't seem to work. I can press the buttons, and nothing happens. If i leave the view and come back, everything appears where it should.
I'll include all the files below. Is there something I'm missing here?
Thanks
Main view displaying the list with two sections
import SwiftUI
import FirebaseFirestoreSwift
struct SessionInvitesView: View {
#Environment(\.presentationMode) private var presentationMode
#FirestoreQuery(collectionPath: "clients") var clients : [Client]
#Binding var sessionViewModel : TrainingSessionViewModel
#State private var searchText: String = ""
#State var refresh : Bool = false
var enrolledClients : [Client] {
return clients.filter { sessionViewModel.session.invites.contains($0.id!) }
}
var availableClients : [Client] {
return clients.filter { !sessionViewModel.session.invites.contains($0.id!) }
}
var searchFilteredClients : [Client] {
if searchText.isEmpty {
return availableClients
} else {
return availableClients.filter {
$0.dogName.localizedCaseInsensitiveContains(searchText) ||
$0.name.localizedCaseInsensitiveContains(searchText) ||
$0.dogBreed.localizedCaseInsensitiveContains(searchText) }
}
}
var backButton: some View {
Button(action: { self.onCancel() }) {
Text("Back")
}
}
var body: some View {
NavigationView {
List {
Section(header: Text("Enrolled")) {
ForEach(enrolledClients) { client in
SessionInviteListItem(client: client, isEnrolled: true, onTap: removeClient)
}
}
Section(header: Text("Others")) {
ForEach(searchFilteredClients) { client in
SessionInviteListItem(client: client, isEnrolled: false, onTap: addClient)
}
}
}
.listStyle(.insetGrouped)
.searchable(text: $searchText)
.navigationTitle("Invites")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(leading: backButton)
}
}
func removeClient(clientId: String) {
self.sessionViewModel.session.invites.removeAll(where: { $0 == clientId })
refresh.toggle()
}
func addClient(clientId: String) {
self.sessionViewModel.session.invites.append(clientId)
refresh.toggle()
}
func dismiss() {
self.presentationMode.wrappedValue.dismiss()
}
func onCancel() {
self.dismiss()
}
}
struct SessionInvitesView_Previews: PreviewProvider {
#State static var model = TrainingSessionViewModel()
static var previews: some View {
SessionInvitesView(sessionViewModel: $model)
}
}
List item view
import SwiftUI
struct SessionInviteListItem: View {
var client : Client
#State var isEnrolled : Bool
var onTap : (String) -> ()
var body: some View {
HStack {
VStack(alignment: .leading) {
HStack {
Text(client.dogName.uppercased())
.bold()
Text("(\(client.dogBreed))")
}
Text(client.name)
.font(.subheadline)
}
Spacer()
Button(action: { onTap(client.id!) }) {
Image(systemName: self.isEnrolled ? "xmark.circle.fill" : "plus.circle.fill")
}
.buttonStyle(.borderless)
.foregroundColor(self.isEnrolled ? .red : .green)
}
}
}
struct SessionInviteListItem_Previews: PreviewProvider {
static func doNothing(_ : String) {}
static var previews: some View {
SessionInviteListItem(client: buildSampleClient(), isEnrolled: false, onTap: doNothing)
}
}
Higher level view used to navigate to this list view
import SwiftUI
import FirebaseFirestoreSwift
struct TrainingSessionEditView: View {
// MARK: - Member Variables
#Environment(\.presentationMode) private var presentationMode
#FirestoreQuery(collectionPath: "clients") var clients : [Client]
#StateObject var sheetManager = SheetManager()
var mode: Mode = .new
var dateManager = DateManager()
#State var viewModel = TrainingSessionViewModel()
#State var sessionDate = Date.now
#State var startTime = Date.now
#State var endTime = Date.now.addingTimeInterval(3600)
var completionHandler: ((Result<Action, Error>) -> Void)?
// MARK: - Local Views
var cancelButton: some View {
Button(action: { self.onCancel() }) {
Text("Cancel")
}
}
var saveButton: some View {
Button(action: { self.onSave() }) {
Text("Save")
}
}
var addInviteButton : some View {
Button(action: { sheetManager.showInvitesSheet.toggle() }) {
HStack {
Text("Add")
Image(systemName: "plus")
}
}
}
// MARK: - Main View
var body: some View {
NavigationView {
List {
Section(header: Text("Details")) {
TextField("Session Name", text: $viewModel.session.title)
TextField("Location", text: $viewModel.session.location)
}
Section {
DatePicker(selection: $sessionDate, displayedComponents: .date) {
Text("Date")
}
.onChange(of: sessionDate, perform: { _ in
viewModel.session.date = dateManager.dateToStr(date: sessionDate)
})
DatePicker(selection: $startTime, displayedComponents: .hourAndMinute) {
Text("Start Time")
}
.onAppear() { UIDatePicker.appearance().minuteInterval = 15 }
.onChange(of: startTime, perform: { _ in
viewModel.session.startTime = dateManager.timeToStr(date: startTime)
})
DatePicker(selection: $endTime, displayedComponents: .hourAndMinute) {
Text("End Time")
}
.onAppear() { UIDatePicker.appearance().minuteInterval = 15 }
.onChange(of: endTime, perform: { _ in
viewModel.session.endTime = dateManager.timeToStr(date: endTime)
})
}
Section {
HStack {
Text("Clients")
Spacer()
Button(action: { self.sheetManager.showInvitesSheet.toggle() }) {
Text("Edit").foregroundColor(.blue)
}
}
ForEach(viewModel.session.invites, id: \.self) { clientID in
self.createClientListElement(id: clientID)
}
.onDelete(perform: deleteInvite)
}
Section(header: Text("Notes")) {
TextField("Add notes here...", text: $viewModel.session.notes)
}
if mode == .edit {
Section {
HStack {
Spacer()
Button("Delete Session") {
sheetManager.showActionSheet.toggle()
}
.foregroundColor(.red)
Spacer()
}
}
}
}
.navigationTitle(mode == .new ? "New Training Session" : "Edit Training Session")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(
leading: cancelButton,
trailing: saveButton)
.actionSheet(isPresented: $sheetManager.showActionSheet) {
ActionSheet(title: Text("Are you sure?"),
buttons: [
.destructive(Text("Delete Session"), action: { self.onDelete() }),
.cancel()
])
}
.sheet(isPresented: $sheetManager.showInvitesSheet) {
SessionInvitesView(sessionViewModel: $viewModel)
}
}
}
func createClientListElement(id: String) -> some View {
let client = clients.first(where: { $0.id == id })
if let client = client {
return AnyView(ClientListItem(client: client))
}
else {
return AnyView(Text("Invalid Client ID: \(id)"))
}
}
func deleteInvite(indexSet: IndexSet) {
viewModel.session.invites.remove(atOffsets: indexSet)
}
// MARK: - Local Event Handlers
func dismiss() {
self.presentationMode.wrappedValue.dismiss()
}
func onCancel() {
self.dismiss()
}
func onSave() {
self.viewModel.onDone()
self.dismiss()
}
func onDelete() {
self.viewModel.onDelete()
self.dismiss()
self.completionHandler?(.success(.delete))
}
// MARK: - Sheet Management
class SheetManager : ObservableObject {
#Published var showActionSheet = false
#Published var showInvitesSheet = false
}
}
struct TrainingSessionEditView_Previews: PreviewProvider {
static var previews: some View {
TrainingSessionEditView(viewModel: TrainingSessionViewModel(session: buildSampleTrainingSession()))
}
}
I'm happy to include any of the other files if you think it would help. Thanks in advance!

How to pass data from one model to another model?

I have two different views each with a model to hold their data. I'm trying to pass the value of one variable in the model to be used in the other model but the value from the first model isn't being passed.
First file
struct Number: View {
#StateObject var model = NumberModel()
var body: some View {
NavigationView {
Form {
Section {
TextField("Enter your first number", text: $model.firstNum)
.keyboardType(.decimalPad)
}
Section {
Text("\(model.firstNum)")
}
}
}
}
}
Model for first file
class NumberModel: ObservableObject {
#Published var firstNum: String
init() {
self.firstNum = ""
}
}
Second file
struct SecondNumber: View {
#StateObject var model = SecondNumberModel()
var body: some View {
NavigationView {
Form {
Section {
TextField("Enter your second number", text: $model.secondNum)
.keyboardType(.decimalPad)
}
Section {
Button {
model.add()
} label: {
Text("Press Me!!!")
}
}
Section {
Text("\(model.total)")
}
}
}
}
}
Model for second file
class SecondNumberModel: ObservableObject {
#ObservedObject var model = NumberModel()
#Published var secondNum: String
#Published var total: Int
init() {
self.secondNum = ""
self.total = 0
}
func add() {
self.total = Int(self.secondNum + self.model.firstNum) ?? 0
}
}
This is the content view
struct ContentView: View {
var body: some View {
TabView {
Number()
.tabItem {
Image(systemName: "circle.fill")
Text("First")
}
SecondNumber()
.tabItem {
Image(systemName: "circle.fill")
Text("Second")
}
}
}
}
I'm trying to get user input from the first file and then send that number to the second file to be added with the second number gathered. But the value of the first number doesn't get passed into the second file's model. Appreciate any help. Thanks.
If you have sibling views that need to share state, that state should be controlled by the parent view. For example, this would work in your case:
class NumberModel: ObservableObject {
#Published var firstNum: String = ""
#Published var secondNum: String = ""
#Published var total: Int = 0
func add() {
self.total = Int(self.secondNum + self.firstNum) ?? 0
}
}
struct Number: View {
#ObservedObject var model : NumberModel
var body: some View {
NavigationView {
Form {
Section {
TextField("Enter your first number", text: $model.firstNum)
.keyboardType(.decimalPad)
}
Section {
Text("\(model.firstNum)")
}
}
}
}
}
struct SecondNumber: View {
#ObservedObject var model : NumberModel
var body: some View {
NavigationView {
Form {
Section {
TextField("Enter your second number", text: $model.secondNum)
.keyboardType(.decimalPad)
}
Section {
Button {
model.add()
} label: {
Text("Press Me!!!")
}
}
Section {
Text("\(model.total)")
}
}
}
}
}
struct ContentView: View {
#StateObject private var appState = NumberModel()
var body: some View {
TabView {
Number(model: appState)
.tabItem {
Image(systemName: "circle.fill")
Text("First")
}
SecondNumber(model: appState)
.tabItem {
Image(systemName: "circle.fill")
Text("Second")
}
}
}
}

Swift - Update List from different View

I have 2 Views in my Swift Project and when I click on the Button on the secondView, I want to update the List in the First View. I don't know how to do it! If I use a static variable in my MainView and then edit this variable from the secondView, it works, but it won't update. And if I don't use static and instead use #State, it would update, but I can't access it from my secondView.
Here is the Code below:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
MainView()
.tabItem() {
VStack {
Image(systemName: "circle.fill")
Text("MainView")
}
}.tag(0)
UpdateOtherViewFromHere()
.tabItem() {
VStack {
Image(systemName: "circle.fill")
Text("SecondView")
}
}.tag(1)
}
}
}
struct MainView: View {
var arrayList: [CreateListItems] = []
init() {
let a = CreateListItems(name: "First Name!")
let b = CreateListItems(name: "Second Name!")
let c = CreateListItems(name: "Third Name!")
arrayList.append(a)
arrayList.append(b)
arrayList.append(c)
}
var body: some View {
return VStack {
ZStack {
NavigationView {
List {
ForEach(arrayList) { x in
Text("\(x.name)")
}
}.navigationBarTitle("Main View")
}
}
}
}
}
struct UpdateOtherViewFromHere: View {
func updateList() {
//Code that should remove "FirstName" from the List in MainView
}
var body: some View {
return VStack {
Button(action: {
updateList()
}) {
Image(systemName: "heart.slash")
.font(.largeTitle)
Text("Click Me!")
}
}
}
}
struct CreateListItems: Identifiable {
var id: UUID = UUID()
var name: String
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You can share it using #State and #Binding if you put
struct ContentView: View {
#State var arrayList: [CreateListItems] = []
struct MainView: View {
#Binding var arrayList: [CreateListItems]
struct UpdateOtherViewFromHere: View {
#Binding var arrayList: [CreateListItems]
or you use the MVVM pattern and store the list in an ObservableObject and use #StateObject/#ObservedObject (source) and use #EnvironmentObject(connection) to share it between your Views.
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
class ParentViewModel: ObservableObject{
#Published var arrayList: [CreateListItems] = []
init(){
addSamples()
}
func addSamples() {
let a = CreateListItems(name: "First Name!")
let b = CreateListItems(name: "Second Name!")
let c = CreateListItems(name: "Third Name!")
arrayList.append(a)
arrayList.append(b)
arrayList.append(c)
}
func updateList() {
let a = CreateListItems(name: "\(arrayList.count + 1) Name!")
arrayList.append(a)
}
}
struct ParentView: View {
#StateObject var vm: ParentViewModel = ParentViewModel()
var body: some View {
TabView {
MainView().environmentObject(vm)
.tabItem() {
VStack {
Image(systemName: "circle.fill")
Text("MainView")
}
}.tag(0)
UpdateOtherViewFromHere().environmentObject(vm)
.tabItem() {
VStack {
Image(systemName: "circle.fill")
Text("SecondView")
}
}.tag(1)
}
}
}
struct MainView: View {
#EnvironmentObject var vm: ParentViewModel
var body: some View {
return VStack {
ZStack {
NavigationView {
List {
ForEach(vm.arrayList) { x in
Text(x.name)
}
}.navigationBarTitle("Main View")
}
}
}
}
}
struct UpdateOtherViewFromHere: View {
#EnvironmentObject var vm: ParentViewModel
var body: some View {
return VStack {
Button(action: {
vm.updateList()
}) {
Image(systemName: "heart.slash")
.font(.largeTitle)
Text("Click Me!")
}
}
}
}

SwiftUI - Present 3 different Views with different parameter

I need to present 3 different Views.
AddListView
ChangeColor
EditListView
They take different paramater. AddListView does not have parameter while ChangeColor and EditListView takes Color and NSManagedObject respectively. However for the sake of simplicity, EditListView's paramter is integer in this example.
I am using .fullScreenCover(item: <#T##Binding<Identifiable?>#>, content: <#T##(Identifiable) -> View#>) for presenting them.
.fullScreenCover(item: $presentedViewType) { type in
if type == .AddListView {
AddListView()
}
else if type == .EditListView {
if let index = selectedIndex {
EditListView(index: index)
}
}
else if type == .ChangeColor {
if let color = selectedColor {
ColorView(color: color)
}
}
}
selectedIndex and selectedColor is nil even though I initialize them before initializing presentedViewType. And hence, an EmptyView is presented.
This is the project.
enum PresentedViewType: Identifiable {
case AddListView
case ChangeColor
case EditListView
var id: Int {
return hashValue
}
}
struct ContentView: View {
#State var presentedViewType: PresentedViewType?
#State var selectedColor: Color?
#State var selectedIndex: Int?
var body: some View {
NavigationView {
List {
Section {
NavigationLink(destination: Text("All")) {
Text("All")
}
.background(Color.blue)
.contextMenu {
Button(action: {
selectedColor = .blue
presentedViewType = .ChangeColor
}) {
Label("Change Color", systemImage: "paintbrush.pointed.fill")
}
}
}
ForEach(0..<10) { index in
NavigationLink(destination: Text("Row Details \(index)")) {
Text("Row \(index)")
}
.contextMenu {
Button(action: {
selectedIndex = index
presentedViewType = .EditListView
}) {
Label("Edit", systemImage: "square.and.pencil")
}
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
presentedViewType = .AddListView
}) {
Label("Add", systemImage: "plus")
}
}
}
.fullScreenCover(item: $presentedViewType) { type in
if type == .AddListView {
AddListView()
}
else if type == .EditListView {
if let index = selectedIndex {
EditListView(index: index)
}
}
else if type == .ChangeColor {
if let color = selectedColor {
ColorView(color: color)
}
}
}
}
}
}
struct ColorView: View {
#Environment(\.presentationMode) var presentationMode
#State var color: Color
var body: some View {
NavigationView {
Text("Color View")
.background(color)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image(systemName: "xmark")
}
}
}
}
}
}
}
struct AddListView: View {
#Environment(\.presentationMode) var presentationMode
#State var text: String = ""
var body: some View {
NavigationView {
TextField("", text: $text)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image(systemName: "xmark")
}
}
}
}
}
}
}
struct EditListView: View {
#Environment(\.presentationMode) var presentationMode
#State var index: Int
var body: some View {
NavigationView {
Text("Row \(index)")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image(systemName: "xmark")
}
}
}
}
}
}
}
I have to mention that they do not have fixed value. They have different value depending on which row you need to edit.
How to pass selectedIndex and selectedColor to EditListView and ColorView respectively?
Update
EditListView takes only selectedIndex while ColorView takes only selectedColor
You need to have #Binding properties inside EditListView and ColorView
struct EditListView: View {
#Binding var selectedIndex: Int?
// rest of view implementation
}
struct ColorView: View {
#Binding var selectedIndex: Int?
// rest of view implementation
}
and then pass the binding in the initialisers
.fullScreenCover(item: $presentedViewType) { type in
if type == .AddListView {
AddListView()
} else if type == .EditListView {
EditListView(index: $selectedIndex)
} else if type == .ChangeColor {
ColorView(color: $selectedColor)
}
}

How to delete from dynamic range using only ForEach and not List

I can't find way to delete from a dynamic array that is being used in a ForEach loop. I've been looking with no luck. Many answers use List or don't have a binding in their ForEach. And I don't want to use list because it's hard to fully customize its design.
Below is a sample code that adds and remove elements from an array. This array is used to display a dynamic list of players.
Removing a player produces an index out of range after unwrapping the optional in ForEach loop.
import SwiftUI
struct GameRecapView: View {
#State private var game = Game(players: [Player(name: "Steph"),Player(name: "Kim")])
#State private var shape = Shape.circle
var body: some View {
VStack {
ForEach(self.game.players ,id: \.id) { player in
PlayerView(
player: self.$game.players[self.game.players.firstIndex(where: {$0.id == player.id})!],
shape: self.$shape)
}
Spacer()
Button(action: {
self.toggleShape()
}) {
Text("Change shape")
}
HStack {
Button(action: {
self.addPlayer(player: Player(name: "Eddye"))
}) {
Text("+")
}
Button(action: {
self.removeLastPlayer()
}) {
Text("-")
}
}
}
}
func toggleShape(){
if self.shape == .circle {
self.shape = .square
} else {
self.shape = .circle
}
}
func addPlayer(player : Player) {
self.game.players.append(player)
}
func removeLastPlayer(){
self.game.players.removeLast()
}
func removeItems(at offsets: IndexSet) {
self.game.players.remove(atOffsets: offsets)
}
}
struct PlayerView: View {
#Binding var player : Player
#Binding var shape : Shape
var letters = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"]
var body: some View {
VStack {
ZStack{
Text(String(self.player.name.first!.uppercased()))
if shape == .square {
Rectangle().stroke().frame(width: 50, height: 50)
} else {
Circle().stroke().frame(width: 50, height: 50)
}
}
Button(action: {
self.player.name = self.letters.randomElement()!
}) {
Text("Change Name")
}
}
}
}
struct Game {
var players : [Player]
}
struct Player : Identifiable {
var id = UUID()
var name : String
}
enum Shape {
case
circle ,
square
}
struct GameRecapView_Previews: PreviewProvider {
static var previews: some View {
GameRecapView()
}
}
check this out: (tested and works)
always work on the single source of truth (as apple calls it) and not on copies. make sure your changes will be done by ObservableObject.
class Data : ObservableObject {
#Published var game = Game(players: [Player(name: "Steph"),Player(name: "Kim")])
}
struct GameRecapView: View {
#EnvironmentObject var data : Data
#State private var shape = Shape.circle
var body: some View {
VStack {
ForEach(self.data.game.players ,id: \.id) { player in
PlayerView(
player: self.data.game.players[self.data.game.players.firstIndex(where: {$0.id == player.id})!],
shape: self.$shape)
}
Spacer()
Button(action: {
self.toggleShape()
}) {
Text("Change shape")
}
HStack {
Button(action: {
self.addPlayer(player: Player(name: "Eddye"))
}) {
Text("+")
}
Button(action: {
self.removeLastPlayer()
}) {
Text("-")
}
}
}
}
func toggleShape(){
if self.shape == .circle {
self.shape = .square
} else {
self.shape = .circle
}
}
func addPlayer(player : Player) {
self.data.game.players.append(player)
}
func removeLastPlayer(){
self.data.game.players.removeLast()
}
func removeItems(at offsets: IndexSet) {
self.data.game.players.remove(atOffsets: offsets)
}
}
struct PlayerView: View {
#EnvironmentObject var data : Data
var player : Player
#Binding var shape : Shape
var letters = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"]
var body: some View {
VStack {
ZStack{
Text(String(self.player.name.first!.uppercased()))
if shape == .square {
Rectangle().stroke().frame(width: 50, height: 50)
} else {
Circle().stroke().frame(width: 50, height: 50)
}
}
Button(action: {
let playerIndex = self.data.game.players.firstIndex(where: {$0.id == self.player.id})
self.data.game.players[playerIndex!].name = self.letters.randomElement()!
}) {
Text("Change Name")
}
}
}
}
struct Game {
var players : [Player]
}
struct Player : Identifiable {
var id = UUID()
var name : String
}
enum Shape {
case
circle ,
square
}
struct ContentView: View {
var body : some View {
Text("wtf")
}
}
struct GameRecapView_Previews: PreviewProvider {
static var previews: some View {
GameRecapView().environmentObject(Data())
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Resources