SwiftUI Passing arguments over to View is not always available - view

I have a problem with passing arguments over to a View in SwiftUI when calling it. I have this View
import SwiftUI
struct GoodsItemFilterView: View {
#Environment(\.presentationMode) var presentationMode
#State var ref1Array: [String] = []
#State var ref2Array: [String] = []
#State var ref3Array: [String] = []
#State var stockStatusArray: [String] = []
#State var zoneArray: [String] = []
#State var selectorRef1 = 0
#State var selectorRef2 = 0
#State var selectorRef3 = 0
#State var selectorStockStatus = 0
#State var selectorZone = 0
var body: some View {
NavigationView {
Form{
Section(header: Text("Zone"), content: {
Picker(selection: $selectorZone, label:
Text("Zone")) {
ForEach(0 ..< zoneArray.count, id:\.self) {
Text(self.zoneArray[$0])
}
}
})
Section(header: Text("References"), content: {
Picker(selection: $selectorRef1, label:
Text("Reference 1")) {
ForEach(0 ..< ref1Array.count, id:\.self) {
Text(self.ref1Array[$0])
}
}
Picker(selection: $selectorRef2, label:
Text("Reference 2")) {
ForEach(0 ..< ref2Array.count, id:\.self) {
Text(self.ref2Array[$0])
}
}
Picker(selection: $selectorRef3, label:
Text("Reference 3")) {
ForEach(0 ..< ref3Array.count, id:\.self) {
Text(self.ref3Array[$0])
}
}
})
Section(header: Text("Status"), content: {
Picker(selection: $selectorStockStatus, label:
Text("Condition")) {
ForEach(0 ..< stockStatusArray.count, id:\.self) {
Text(self.stockStatusArray[$0])
}
}
})
Button(action: {
self.selectorZone = 0
self.selectorRef1 = 0
self.selectorRef2 = 0
self.selectorRef3 = 0
self.selectorStockStatus = 0
}, label: {
HStack(){
Spacer()
Image(systemName: "return")
Text("Reset filters")
Spacer()
}
})
}.navigationBarTitle("Filter")
.navigationBarItems(leading: (
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Cancel")
}
)
), trailing: (
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Done")
}
)
))
}.onAppear{
self.ref1Array.insert("***ALL***", at: 0)
self.ref2Array.insert("***ALL***", at: 0)
self.ref3Array.insert("***ALL***", at: 0)
self.stockStatusArray.insert("***ALL***", at: 0)
self.zoneArray.insert("***ALL***", at: 0)
}
}
}
struct GoodsItemFilter_Previews: PreviewProvider {
static var previews: some View {
GoodsItemFilterView(ref1Array: ["MAX100", "MAX101", "MAX102"], ref2Array: ["REF2_100", "REF2_101"], ref3Array: ["REF3_100", "REF3_101"])
}
}
and when I call it I can pass over the values of the arrays as arguments:
GoodsItemFilterView(ref1Array: ["MAX100", "MAX101", "MAX102"], ref2Array: ["REF2_100", "REF2_101"], ref3Array: ["REF3_100", "REF3_101"])
Now I have another view which is basically a copy of this one with a few changed names etc
//
// OrderHeaderFilter.swift
// WMS Toolbox
//
// Created by Max on 2020-01-24.
// Copyright © 2020 Max. All rights reserved.
//
import SwiftUI
//import Combine
struct OrderHeaderFilterView: View {
#Environment(\.presentationMode) var presentationMode
#State var orderTypeArray: [String] = []
#State var carrierArray: [String] = []
#State var fromStatus2 = UserDefaults.standard.string(forKey: "view.orderHeaderFilter.fromStatus")
// #State private var fromStatus2 = "040"
#State private var direction = ""
#State private var fromStatus = ""
#State private var toStatus = ""
#State private var orderType = ""
#State var selectorOrderType = 0
#State var selectorCarrier = 0
#State private var selectorIndex = 1
#State private var fromStatusSelectorIndex = 6
#State private var toStatusSelectorIndex = 2
#State private var directions = ["Inbound","Outbound","Both"]
private var orderStatusFromArray: [String] = ["005", "010", "022", "025", "030", "035", "040", "045", "046", "047", "060"]
private var orderStatusToArray: [String] = ["005", "010", "022", "025", "030", "035", "040", "045", "046", "047", "060"]
#State var orderStatus = OrderStatus.s05
enum OrderStatus: String, CaseIterable, Identifiable {
case s05 = "005"
case s10 = "010"
case s22 = "022"
case s25 = "025"
case s30 = "030"
case s35 = "035"
case s40 = "040"
case s45 = "045"
case s46 = "046"
case s60 = "060"
var id: String { rawValue }
}
enum Direction: String, CaseIterable{
case outbound = "1"
case inbound = "2"
case both = "3"
init(type: String) {
switch type {
case "1": self = .outbound
case "2": self = .inbound
case "3": self = .both
default: self = .both
}
}
var text: String {
switch self {
case .outbound: return "Outbound"
case .inbound: return "Inbound"
case .both: return "Both"
}
}
}
init(){
//nothing here
}
var body: some View {
return NavigationView{
Form{
HStack{
Text("Direction")
Spacer()
Picker(selection: $direction, label:
Text("Direction")) {
ForEach(directions, id:\.self) {
status in
Text(status)
}
}
.pickerStyle(SegmentedPickerStyle())
}
Picker(selection: $fromStatus, label:
Text("From Status")) {
ForEach(orderStatusFromArray, id:\.self) {
status in
Text(status)
}
}
Picker(selection: $toStatus, label:
Text("To Status")) {
ForEach(orderStatusFromArray, id:\.self) {
status in
Text(status)
}
}
}.navigationBarTitle("Filter")
.navigationBarItems(leading: (
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Cancel")
}
)
), trailing: (
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Done")
}
)
))
}.onAppear{
self.direction = UserDefaults.standard.string(forKey: "view.orderHeaderFilter.direction")!
self.fromStatus = UserDefaults.standard.string(forKey: "view.orderHeaderFilter.fromStatus")!
self.toStatus = UserDefaults.standard.string(forKey: "view.orderHeaderFilter.toStatus")!
self.orderTypeArray.insert("***ALL***", at: 0)
self.carrierArray.insert("***ALL***", at: 0)
}
}
}
struct OrderHeaderFilter_Previews: PreviewProvider {
static var previews: some View {
OrderHeaderFilterView()
}
}
and when I call it, it is not prompting me to pass over the arrays as arguments:
OrderHeaderFilterView()
What is the difference between those 2 views that the one is asking for arguments on initilization and the other one isn't? To be clear, in the end I want to pass over the arguments, so GoodsItemFilterView() is doing exactly what I need.

EnvironmentObjects are not passed via init method they are implicitly injected. The orderTypeArray and carrierArray have already initial values. So OrderHeaderFilterView() does not prompt you for arguments.

Found the issue(s):
I had this piece in the code
init(){
//nothing here
}
This needs to be removed, otherwise it will not ask for any variables.
The other issue is the one I don't understand:
private var orderStatusFromArray: [String] = ["005", "010", "022", "025", "030", "035", "040", "045", "046", "047", "060"]
private var orderStatusToArray: [String] = ["005", "010", "022", "025", "030", "035", "040", "045", "046", "047", "060"]
If I change the var to let, it works as expected. Another option is to remove the private at the beginning. So it looks like as soon as you have a
private var ...
in your code, all arguments become private. Maybe I am missing something here but that seems like a bug to me.

Related

How to sort List ForEach Swiftui

Hi there
I'm newbie for SwiftUI, and I want to sort the "expireDate" , then use forEach to display the view according to the expireDate, how to???
sorry for my messy code, coding is really not easy.
will be much appreciate if someone can help
Here is the data
import Foundation
struct CardData: Identifiable, Codable {
let id: UUID
var cardName: String
var cardNumber: String
var expireDate: Date
var theme: Theme
var history: [History] = []
init(id: UUID = UUID(), cardName: String, cardNumber: String, expireDate: Date, theme: Theme) {
self.id = id
self.cardName = cardName
self.cardNumber = cardNumber
self.expireDate = expireDate
self.theme = theme
}
}
extension CardData {
struct Data {
var cardName: String = ""
var cardNumber: String = ""
var expireDate: Date = Date.now
var theme: Theme = .orange
}
var data: Data {
Data(cardName: cardName, cardNumber: cardNumber, expireDate: expireDate, theme: theme)
}
mutating func update(from data: Data) {
cardName = data.cardName
cardNumber = data.cardNumber
expireDate = data.expireDate
theme = data.theme
}
init(data: Data) {
cardName = data.cardName
cardNumber = data.cardNumber
expireDate = data.expireDate
theme = data.theme
id = UUID()
}
}
And here is the view
import SwiftUI
struct CardView: View {
#Binding var datas: [CardData]
#Environment(\.scenePhase) private var scenePhase
#State private var isPresentingNewCardView = false
#State private var newCardData = CardData.Data()
let saveAction: () -> Void
#EnvironmentObject var launchScreenManager: LaunchScreenManager
#State private var confirmationShow = false
var body: some View {
List {
ForEach($datas) { $data in
NavigationLink(destination: DetailView(cardData: $data)){
CardDataView(cardData: data)
}
.listRowBackground(data.theme.mainColor)
}
.onDelete(perform: deleteItems)
}
.navigationTitle("Expiry Date")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Button(action: {
isPresentingNewCardView = true
}) {
Image(systemName: "plus")
}
.accessibilityLabel("New data")
}
.sheet(isPresented: $isPresentingNewCardView) {
NavigationView {
DetailEditView(data: $newCardData)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Dismiss") {
isPresentingNewCardView = false
newCardData = CardData.Data()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Add") {
let newData = CardData(data: newCardData)
datas.append(newData)
isPresentingNewCardView = false
newCardData = CardData.Data()
}
}
}
}
}
.onChange(of: scenePhase) { phase in
if phase == .inactive { saveAction() }
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
launchScreenManager.dismiss()
}
}
}
func deleteItems(at offsets: IndexSet) {
datas.remove(atOffsets: offsets)
}
}
Hi there
I'm newbie for SwiftUI, and I want to sort the "expireDate" , then use forEach to display the view according to the expireDate, how to???
sorry for my messy code, coding is really not easy.
will be much appreciate if someone can help
You can sort the datas in place, before you use it in the ForEach,
when you create the datas for example. Like this:
datas.sort(by: { $0.expireDate > $1.expireDate}).
Or
you can sort the datas just in the ForEach,
like this, since you have bindings,
ForEach($datas.sorted(by: { $0.expireDate.wrappedValue > $1.expireDate.wrappedValue})) { $data ...}
Note with this ForEach($datas.sorted(by: ...), when you do your func deleteItems(at offsets: IndexSet),
you will have to get the index in the sorted array, and delete the equivalent in the original one.
EDIT-1:
updated func deleteItems:
func deleteItems(at offsets: IndexSet) {
let sortedArr = datas.sorted(by: { $0.expireDate > $1.expireDate})
for ndx in offsets {
if let cardIndex = datas.firstIndex(where: { $0.id == sortedArr[ndx].id }) {
datas.remove(at: cardIndex)
}
}
}
Note you may want to put let sortedArr = datas.sorted(by: { $0.expireDate > $1.expireDate}) somewhere else (eg. .onAppear) instead of evaluating this, every time you use deleteItems

The picker doesn't update the number of segments it has when a value is changed during execution

I have recently started learning swiftUI and I'm facing some issues here. This is the code:
struct ContentView: View {
#State private var measurementType = 0
#State private var inputValue = ""
#State private var inputUnit = 0
#State private var outputUnit = 1
var outputValue = ""
let measurementTypes = ["Temp", "Length", "Time", "Volume"]
var typeDictionary = [
["Celsius", "Fahrenheit", "Kelvin"],
["Meters", "Kilometers", "Feet", "Yards", "Miles"],
["Seconds", "Minutes", "Hours", "Days"],
["Milliliters", "Liters", "Cups", "Pints", "Gallons"]
]
var body: some View {
NavigationView {
Form {
Section(header: Text("Choose the type of measurement")) {
Picker("The type of measurement", selection: $measurementType) {
ForEach(0 ..< measurementTypes.count) {
Text("\(measurementTypes[$0])")
}
}
.id(measurementType)
.pickerStyle(SegmentedPickerStyle())
}
Section {
TextField("Enter the value", text: $inputValue)
.keyboardType(.decimalPad)
}
Section(header: Text("Choose the input unit")) {
Picker("The input unit", selection: self.$inputUnit) {
ForEach(0 ..< typeDictionary[measurementType].count) {
Text("\(typeDictionary[measurementType][$0])")
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("Choose the output unit")) {
}
Section(header: Text("Converted Value")) {
Text("")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Whenever I change the measurementType while the code is running, the values of the picker in the input units section update, but the number of segments don't. If I start with the value of measurementType as 1, then the app simply crashes when I choose Temp or Time from the picke.
When using ForEach, you need to provide an id so SwiftUI can uniquely identify each element in the array and know when to redraw the view. You can use id: \.self to use the item's value as its id.
Here's an updated working version:
import SwiftUI
struct ContentView: View {
#State private var measurementType = 0
#State private var inputValue = ""
#State private var inputUnit = 0
#State private var outputUnit = 1
var outputValue = ""
let measurementTypes = ["Temp", "Length", "Time", "Volume"]
var typeDictionary = [
["Celsius", "Fahrenheit", "Kelvin"],
["Meters", "Kilometers", "Feet", "Yards", "Miles"],
["Seconds", "Minutes", "Hours", "Days"],
["Milliliters", "Liters", "Cups", "Pints", "Gallons"]
]
var body: some View {
NavigationView {
Form {
Section(header: Text("Choose the type of measurement")) {
Picker("The type of measurement", selection: $measurementType) {
ForEach(0 ..< measurementTypes.count, id: \.self) {
Text("\(measurementTypes[$0])")
}
}
.id(measurementType)
.pickerStyle(SegmentedPickerStyle())
}
Section {
TextField("Enter the value", text: $inputValue)
.keyboardType(.decimalPad)
}
Section(header: Text("Choose the input unit")) {
Picker("The input unit", selection: self.$inputUnit) {
ForEach(0 ..< typeDictionary[measurementType].count, id: \.self) {
Text("\(typeDictionary[measurementType][$0])")
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("Choose the output unit")) {
}
Section(header: Text("Converted Value")) {
Text("")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Work out time 1.5 hours ahead using current time and display this in the list

I have the following code someone presses on the Table and its displays the current time which is the arrival time of a customer.
I want to display the time they must leave by, next to the current time this will always be 1.5 hours ahead I can not work out how to do this. everything I have tried comes back with an error.
Still new to Xcode
any help would be great
import SwiftUI
struct TimeListView: View {
#State var tableOne = false
#State var tableTwo = false
#State var tableThree = false
var body: some View {
// Title
VStack {
Text("Arrival Times")
.font(.title)
.fontWeight(.bold)
// List View
List {
// Table 1
HStack {
Button(action: {
self.tableOne.toggle()
}, label: {
Text("Table 1 -")
})
if tableOne {
Text(getCurrentTime())
}
}
// Table 2
HStack {
Button(action: {
self.tableTwo.toggle()
}, label: {
Text("Table 2 -")
})
if tableTwo {
Text(getCurrentTime())
}
}
// Table 3
HStack {
Button(action: {
self.tableThree.toggle()
}, label: {
Text("Table 3 -")
})
if tableThree {
Text(getCurrentTime())
}
}
}
}
}
}
struct TimeListView_Previews: PreviewProvider {
static var previews: some View {
TimeListView()
}
}
// Get Current Time Function
func getCurrentTime() -> String {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_UK_POSIX")
dateFormatter.dateFormat = "HH:mm"
return dateFormatter.string(from: Date())
you need make a date and add 1.5 hour to it, also you forgot create 3 deferent State for them.
import SwiftUI
struct ContentView: View {
var body: some View {
TimeListView()
}
}
struct TimeListView: View {
#State private var table1: Bool = false
#State private var table2: Bool = false
#State private var table3: Bool = false
#State private var timeForShow1: String?
#State private var timeForShow2: String?
#State private var timeForShow3: String?
var body: some View {
VStack {
Text("Arrival Times")
.font(.title)
.fontWeight(.bold)
List {
HStack {
Text("Table 1 -")
.onTapGesture {
if table1 { table1.toggle() }
else { timeForShow1 = getCurrentTime; table1.toggle() }
}
if table1 { Text(timeForShow1 ?? "not available!") }
}
HStack {
Text("Table 2 -")
.onTapGesture {
if table2 { table2.toggle() }
else { timeForShow2 = getCurrentTime; table2.toggle() }
}
if table2 { Text(timeForShow2 ?? "not available!") }
}
HStack {
Text("Table 3 -")
.onTapGesture {
if table3 { table3.toggle() }
else { timeForShow3 = getCurrentTime; table3.toggle() }
}
if table3 { Text(timeForShow3 ?? "not available!") }
}
}
}
}
}
var getCurrentTime: String? {
if let date = Calendar.current.date(byAdding: .minute, value: 90, to: Date()) {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_UK_POSIX")
dateFormatter.dateFormat = "HH:mm"
return dateFormatter.string(from: date)
}
else {
return nil
}
}
You'll probably get a half dozen different ways of doing this but this version allows for adaptive/reusable code. You can easily add a Table by adding one to the var tables: [Table] everything would adjust automatically.
import SwiftUI
class Table: ObservableObject {
let id: UUID = UUID()
#Published var name: String
#Published var status: Bool
#Published var entryTime: Date
var exitTime: Date{
return entryTime.addingTimeInterval(60*60*1.5)
}
init(name: String, status: Bool = false, entryTime: Date = Date.init(timeIntervalSince1970: 0)) {
self.name = name
self.status = status
self.entryTime = entryTime
}
}
struct TimeListView: View {
#State var tables: [Table] = [Table(name: "Table 1 -"), Table(name: "Table 2 -"), Table(name: "Table 3 -")]
var body: some View {
VStack{
Text("Arrival Times")
.font(.title)
.fontWeight(.bold)
List {
ForEach(tables, id: \.id, content: { table in
TableView(table: table)
})
}
}
}
}
struct TableView: View {
#ObservedObject var table: Table
var body: some View {
HStack {
Button(action: {
table.status.toggle()
table.entryTime = Date()
}, label: {
Text(table.name)
})
if table.status{
Text(table.entryTime, formatter: dateFormatter)
Text(table.exitTime, formatter: dateFormatter)
}
}
}
}
struct TimeListView_Previews: PreviewProvider {
static var previews: some View {
TimeListView()
}
}
var dateFormatter: DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_UK_POSIX")
dateFormatter.dateFormat = "HH:mm"
return dateFormatter
}

swiftui textfield is extremely laggy

I'm having issues where as more text is being typed into the textfield, the application (Mac) becomes more and more laggy.
class Action: ObservableObject, Identifiable {
var id = String(UUID().uuidString.prefix(7))
#Published var actionType = ActionType.display
#Published var arguments = [String:ValueType]()
}
struct AnalyticsKeyValuePairView: View {
var state: Action
let initialKey : String
#State var stateKey: String
#State var value: String
init(state: Action, initialKey: String) {
self.initialKey = initialKey
self.state = state
self._stateKey = State(initialValue: initialKey)
self._value = State(initialValue: state.arguments[initialKey]!.stringValue!)
}
var body: some View {
HStack {
TextField("key", text: $stateKey) { (b) in
} onCommit: {
if initialKey != stateKey {
state.setArgument(nil, forKey: initialKey)
}
state.setArgument(.string(value), forKey: stateKey)
}
Spacer()
TextField("value", text: $value) { (_) in
} onCommit: {
if initialKey != stateKey {
state.arguments[initialKey] = nil
}
state.setArgument(.string(value), forKey: stateKey)
}
}
}
in instruments you can see a lot core animation activity

How do I get a dropped pin inside a MapView to save coordinates and present them as a String using SwiftUI?

I made a Form using SwiftUI, in which I have a section that allows a user to press on a button that takes them to a MapKit View. Once inside the map view, the user can press the "+" button to place a pin on a map. This takes them to the Edit View where they can enter text inside a TextField to label the pin (see screenshot below). I have been stuck here for the past few days attempting to save the pin's coordinates or even the user's input inside the TextField to return it as text (either as city, state or country) inside the Form.
Form -> Map View -> Edit View
Here are some code snippets.
1) From FormView:
import SwiftUI
import MapKit
struct FormView: View {
#State private var selectedTitle = ""
#State var meuf: Meuf
#State private var meufs = [Meuf]()
#State private var show = false
#State private var singleIsPresented = false
#Environment(\.presentationMode) var presentationMode
let newMeuf : Bool
#EnvironmentObject var meufStorage : MeufStorage
#State private var showMap = false
var body: some View {
NavigationView{
Form{
//MARK: LOCATION
Section{
HStack {
Button(action: { self.showMap = true }) {
Image(systemName: "mappin.and.ellipse")
}
.sheet(isPresented: $showMap) {
LocationMap(showModal: self.$showMap)
}
Text("Pin your location")
.font(.subheadline)
}
}
// MARK: [ SAVE ENTRY ]
Section {
Button(action: {
if self.newMeuf {
self.saveData()
self.meufStorage.meufs.append(self.meuf)
} else {
for x in 0..<self.meufStorage.meufs.count {
if self.meufStorage.meufs[x].id == self.meuf.id {
self.meufStorage.meufs[x] = self.meuf
}
}
}
self.presentationMode.wrappedValue.dismiss()
}) {
HStack{
Spacer()
Text("Save")
Spacer()
}
}.disabled(meuf.title.isEmpty)
}
}.navigationBarTitle(Text(meuf.title))
}
}
//Get file directory url
func getFileDirectory() -> URL{
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
//Load data from file directory
func loadData(){
let filename = getFileDirectory().appendingPathComponent("SavedPlaces")
do {
let data = try Data(contentsOf: filename)
meufs = try JSONDecoder().decode([Meuf].self, from: data)
}catch{
debugPrint(error)
}
}
//Save data to file directory
func saveData(){
let filename = getFileDirectory().appendingPathComponent("SavedPlaces")
let data = try? JSONEncoder().encode(self.meufs)
do{
try data?.write(to: filename, options: [.atomic , .completeFileProtection])
}catch{
debugPrint(error)
}
}
}
struct FormView_Previews: PreviewProvider {
static var previews: some View {
FormView(meuf: Meuf(), newMeuf: true)
}
}
2) From LocationMap:
import SwiftUI
import MapKit
struct LocationMap: View {
#State private var centerCoordinate = CLLocationCoordinate2D()
#State private var locations = [CodableMKPointAnnotation]()
#State private var selectedPlace: MKPointAnnotation?
#State private var showingPlaceDetails = false
#State private var showingEditScreen = false
#Binding var showModal: Bool
var body: some View {
ZStack{
MapView(centerCoordinate: $centerCoordinate, annotations: locations, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails)
.edgesIgnoringSafeArea(.all)
Circle()
.fill(Color.blue)
.opacity(0.3)
.frame(width: 32, height: 32)
VStack {
Spacer()
HStack{
Spacer()
Button(action:{
let newLocation = CodableMKPointAnnotation()
newLocation.title = ""
newLocation.coordinate = self.centerCoordinate
self.locations.append(newLocation)
self.selectedPlace = newLocation
self.showingEditScreen = true
}){
Image(systemName: "plus")
}
.padding()
.background(Color.black.opacity(0.7))
.foregroundColor(Color.white)
.clipShape(Circle())
.shadow(radius: 0.7)
.padding([.trailing , .bottom])
}
}
.padding()
}.alert(isPresented: $showingPlaceDetails) {
Alert(title: Text(selectedPlace?.title ?? "Unknown"), message: Text(selectedPlace?.subtitle ?? "Missing place information."), primaryButton: .default(Text("OK")), secondaryButton: .default(Text("Edit")) {
self.showingEditScreen = true
}
)
}
.sheet(isPresented: $showingEditScreen, onDismiss: savedData) {
if self.selectedPlace != nil {
EditView(placemark: self.selectedPlace!)
}
}
.onAppear(perform: loadData)
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
func loadData() {
let filename = getDocumentsDirectory().appendingPathComponent("Saved Places")
do {
let data = try Data(contentsOf: filename)
locations = try JSONDecoder().decode([CodableMKPointAnnotation].self, from: data)
} catch {
print("Unable to load saved data.")
}
}
func savedData() {
do {
let filename = getDocumentsDirectory().appendingPathComponent("SavedPlaces")
let data = try JSONEncoder().encode(self.locations)
try data.write(to: filename, options: [.atomicWrite, .completeFileProtection])
} catch {
print("Unable to save data")
}
}
}
struct LocationMap_Previews: PreviewProvider {
static var previews: some View {
LocationMap(showModal: .constant(true))
}
}
3) From MapView:
import MapKit
import Combine
import SwiftUI
struct MapView: UIViewRepresentable {
#Binding var centerCoordinate: CLLocationCoordinate2D
var annotations: [MKPointAnnotation]
#Binding var selectedPlace: MKPointAnnotation?
#Binding var showingPlaceDetails: Bool
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<MapView>) {
if annotations.count != uiView.annotations.count{
uiView.removeAnnotations(uiView.annotations)
uiView.addAnnotations(annotations)
}
}
///Coordinator class for passing data
class Coordinator: NSObject , MKMapViewDelegate{
let parent: MapView
init(_ parent: MapView){
self.parent = parent
}
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
parent.centerCoordinate = mapView.centerCoordinate
}
//Gets called whenever the rightCalloutAccessory is tapped
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
guard let placeMark = view.annotation as? MKPointAnnotation else {return}
parent.selectedPlace = placeMark
parent.showingPlaceDetails = true
}
//Customizes the way the marker looks
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "PlaceMark"
var annotationview = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationview == nil {
annotationview = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationview?.canShowCallout = true
annotationview?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}else {
annotationview?.annotation = annotation
}
return annotationview
}
}
func makeCoordinator() -> MapView.Coordinator {
Coordinator(self)
}
}
extension MKPointAnnotation {
static var example: MKPointAnnotation {
let annotation = MKPointAnnotation()
annotation.title = "Montreal"
annotation.subtitle = "Home of French Canadians"
annotation.coordinate = CLLocationCoordinate2D(latitude: 45.5, longitude: -73.58)
return annotation
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(centerCoordinate: .constant(MKPointAnnotation.example.coordinate), annotations: [MKPointAnnotation.example], selectedPlace: .constant(MKPointAnnotation.example), showingPlaceDetails: .constant(false))
}
}
4) From Edit View:
import SwiftUI
import MapKit
struct EditView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var placemark: MKPointAnnotation
var body: some View {
NavigationView {
Form {
Section {
TextField("Place name", text: $placemark.wrappedTitle)
TextField("Description", text: $placemark.wrappedSubtitle)
}
}
.navigationBarTitle("Edit place")
.navigationBarItems(trailing: Button("Done") {
self.presentationMode.wrappedValue.dismiss()
})
}
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView(placemark: MKPointAnnotation.example)
}
}
5) MKPointAnnotation Codable
import Foundation
import MapKit
class CodableMKPointAnnotation: MKPointAnnotation , Codable {
enum codingKeys: CodingKey {
case title ,subtitle , longitude , latitude
}
override init() {
super.init()
}
public required init(from decoder: Decoder) throws{
super.init()
let container = try decoder.container(keyedBy: codingKeys.self)
title = try container.decode(String.self, forKey: .title)
subtitle = try container.decode(String.self, forKey: .subtitle)
let latitude = try container.decode(CLLocationDegrees.self, forKey: .latitude)
let longitude = try container.decode(CLLocationDegrees.self, forKey: .longitude)
coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: codingKeys.self)
try container.encode(title, forKey: .title)
try container.encode(subtitle, forKey: .subtitle)
try container.encode(coordinate.latitude, forKey: .latitude)
try container.encode(coordinate.longitude, forKey: .longitude)
}
}
6) MKPointAnnotation Object
import MapKit
extension MKPointAnnotation: ObservableObject{
public var wrappedTitle: String{
get{
self.title ?? "No Title"
}
set{
self.title = newValue
}
}
public var wrappedSubtitle: String{
get{
self.subtitle ?? "No information on this location"
}
set{
self.subtitle = newValue
}
}
}
7) Meuf & MeufStorage:
import Foundation
struct Meuf: Identifiable, Encodable, Decodable {
var id = UUID()
var img = ""
var title = ""
var rating = 3.0
var seen = false
var seenDate = ""
}
class MeufStorage: ObservableObject {
#Published var meufs = [Meuf]()
}
8) Scene Delegate:
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let meufStorage = MeufStorage()
let contentView = MeufList().environment(\.managedObjectContext, context).environmentObject(meufStorage)
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
func sceneDidEnterBackground(_ scene: UIScene) {
(UIApplication.shared.delegate as? AppDelegate)?.saveContext()
}
}
Hello I managed to get the solution.
Will add code only which is changed
// Made this a ObservableObject
class Meuf: Identifiable, Codable, ObservableObject {
var id = UUID()
var img = ""
var title = ""
var rating = 3.0
var seen = false
var seenDate = ""
var locations = [CodableMKPointAnnotation]() // We need this to keep the track of locations
}
FormView
struct FormView: View {
#State private var selectedTitle = ""
#ObservedObject var meufObject = Meuf() // This is new will help to keep track of the added locations
#State private var meufs = [Meuf]()
#State private var show = false
#State private var singleIsPresented = false
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var meufStorage : MeufStorage
#State private var showMap = false
var body: some View {
NavigationView{
Form {
List {
// This will list the added locations now
ForEach(self.meufObject.locations, id: \.self) { location in
LocationView(location: location)
}
}
//MARK: LOCATION
Section{
HStack {
Button(action: { self.showMap = true }) {
Image(systemName: "mappin.and.ellipse")
}
.sheet(isPresented: $showMap) {
LocationMap(meufObject: self.meufObject, showModal: self.$showMap)
}
Text("Pin your location")
.font(.subheadline)
}
}
// MARK: [ SAVE ENTRY ]
Section {
Button(action: {
// Handle save action
}) {
HStack{
Spacer()
Text("Save")
Spacer()
}
}
}
}
}
}
// Rest of your code stays same .......
}
// Added this new view to render the location view
struct LocationView: View {
var location : CodableMKPointAnnotation
var body: some View {
Text(location.title ?? "title" )
}
}
LocationMap
struct LocationMap: View {
#ObservedObject var meufObject: Meuf // This is new will help to keep track of the added locations
#State private var centerCoordinate = CLLocationCoordinate2D()
#State private var locations = [CodableMKPointAnnotation]()
#State private var selectedPlace: MKPointAnnotation?
#State private var showingPlaceDetails = false
#State private var showingEditScreen = false
#Environment(\.presentationMode) var presentationMode
#Binding var showModal: Bool
var body: some View {
ZStack{
MapView(centerCoordinate: $centerCoordinate, annotations: locations, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails)
.edgesIgnoringSafeArea(.all)
Circle()
.fill(Color.blue)
.opacity(0.3)
.frame(width: 32, height: 32)
VStack {
Spacer()
HStack{
Spacer()
Button(action:{
let newLocation = CodableMKPointAnnotation()
newLocation.title = ""
newLocation.coordinate = self.centerCoordinate
self.locations.append(newLocation)
self.meufObject.locations = self.locations // By doing this we will be able to pass it to main screen
self.selectedPlace = newLocation
self.showingEditScreen = true
}){
Image(systemName: "plus")
}
.padding()
.background(Color.black.opacity(0.7))
.foregroundColor(Color.white)
.clipShape(Circle())
.shadow(radius: 0.7)
.padding([.trailing , .bottom])
// Rest stays same as your implementation
}
}
.padding()
}
}
// Rest stays same as your implementation
}

Resources