I am working on an app that saves transaction entries. The user is requested to enter date / time, select local currency, payment type, category, description, and expense amount. When entering the description and entry amount they may tap the Done button above the keyboard to dismiss the keyboard. The problem is that quite commonly the Done button doesn't appear and with the decimal keyboard there is no way to dismiss the keyboard. I would say that the Done button appears only about 30-40% of the time.
In the EntryView structure, I define the @FocusStatus and pass it down to GetDescription(myFocus: myFocus) and GetMoney(myFocus: myFocus). Further down in EntryView under the toolbar I setting up the Done Button operation and resetting myFocus to nil.
In GetMoney I am using the binding var myFocus: FocusState<EntryView.Field?>.Binding.
Finally below the GetMoney TextField("Amount" ... there is the .focused(myFocus, equals: .ckAmt
GetDescription is not much of a problem because the default keyboard has a built in done button (.submitLabel(.done)).
I have Focus / Done operation in four other locations in my app where this works just fine. I need help figuring out why it fails part of the time.
struct EntryView: View {
enum Field: Hashable {
case ckDsc
case ckAmt
}
@FocusState private var myFocus: Field?
var body: some View {
GeometryReader { g in
VStack {
Form {
GetDate(entryDT: $entryDT)
GetCurrency()
GetPaymentType(g: g, entryPT: $entryPT)
GetCategory(entryCat: $entryCat)
GetDescription(g: g, entryDsc: $entryDsc, myFocus: $myFocus)
GetMoney(g: g, moneyD: $moneyD, myFocus: $myFocus)
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.toolbarBackground(Color.orange, for: .navigationBar)
.toolbar(content: {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
myFocus = nil
}
}
ToolbarItem(placement: .principal) {
VStack (alignment: .leading) {
HStack {
Text("Expense Entry")
.font(.title)
.bold()
.kerning(1.5)
.padding(.leading, 10)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Button {
myFocus = nil
if moneyD != 0.0 {
self.saveButton() // button pressed
}
if !zeroEntry {
dismiss()
}
} label: {
Text("Save")
.bold()
.padding(.trailing, 10)
.tint(.blue)
}
.disabled(moneyD == 0.0)
}
}
}
})
}
}
// Entry save button has been pressed: save data to swiftdata
func saveButton() {
}
}
struct GetMoney: View {
var g: GeometryProxy
@Binding var moneyD: Double?
var myFocus: FocusState<EntryView.Field?>.Binding
var body: some View {
Section(header: Text("Enter Amount")) {
HStack {
TextField("Amount", value: $moneyD, format: .number.precision(.fractionLength(0...2)))
.focused(myFocus, equals: .ckAmt)
.font(.headline)
.padding(.vertical, 10)
.keyboardType(.decimalPad)
}
}
}
}
}
Post
Replies
Boosts
Views
Activity
Now that foregroundColor() has been depreciated, I have noticed that I can't substitute foregroundStyle() for foregroundColor() in UISegmentedControl.appearance:
UISegmentedControl.appearance().setTitleTextAttributes([.foregoundColor: UIColor.orange], for: .selected)
Is there an alternate method to set the segmented control foreground color?
Testing in-app purchase with the App Store Connect / Xcode configuration file allows testing of approximately 43 non-consumable product errors.
Testing my non-consumable in-app purchase with the new ProductView on my app for these various faults doses’t appear to be very promising.
Loading errors: all result in a blank screen and spinning wheel without producing an alert.
Purchase errors: Some of these work producing readable comments. Purchase (not entitled) and Purchase (system error) just produce alert boxes with an OK button.
Purchase (network) produces the alert “The operation could not be completed. (NSURLErrorDomain error - 1009.)”
Verification errors, App Store Sync, and App Transaction errors: These purchases are successful and produce no alerts.
Am I missing a method that handles these errors?
With ProductView do I need to use a do / catch block in my viewModel?
Can I release this app for review working like this?
struct StoreView: View {
@StateObject private var store = StoreModel()
var body: some View {
GeometryReader { g in
VStack {
Spacer()
Text("\(mainTitle)")
.font(.title)
.padding(.bottom, 25)
ProductView(id: "xxxx") { _ in
Image(systemName: "xxxx")
.resizable()
.scaledToFit()
} placeholderIcon: {
ProgressView()
}
.productViewStyle(.large)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color( "darkGreen"), lineWidth: 5)
.frame(width: g.size.width * 0.90, height: g.size.height * 0.35 )
)
.frame(maxWidth: .infinity, alignment: .center)
Spacer()
}
}
}
}
@MainActor final class StoreModel: ObservableObject {
@Published private(set) var products: [Product] = []
@Published private(set) var activeTransactions: Set<StoreKit.Transaction> = []
@Published var gotRefund = false // use to reset purchase
private var updates: Task<Void, Never>?
private var productIDs = ["xxxxx"]
// app wide purchase status
@AppStorage(StorageKeys.purStatus.rawValue) var storeStatus: Bool = false
init()
{
updates = Task {
for await update in StoreKit.Transaction.updates {
if let transaction = try? update.payloadValue {
await fetchActiveTransactions()
// check transaction.revocationDate for refund
if transaction.revocationDate != nil {
self.storeStatus = false // requesting refund
self.gotRefund = true
} else {
self.storeStatus = true // making purchase
}
await transaction.finish()
}
}
}
} // end init
deinit {
updates?.cancel()
}
// update the current entitlements
func fetchActiveTransactions() async {
var activeTransactions: Set<StoreKit.Transaction> = []
for await entitlement in StoreKit.Transaction.currentEntitlements {
if let transaction = try? entitlement.payloadValue {
activeTransactions.insert(transaction)
}
}
self.activeTransactions = activeTransactions
}
}
I have a map (iOS 17) that displays the three Apple maps types (standard, hybrid, and imagery). When the hybrid or imagery map is displayed for the first time it is zoomed in to the max and out-of-focus. It appears to be a problem with the span. I would like to set the span to fix this, but I'm not sure if span has a new name or is no longer used.
In previous versions of iOS I had a region that defined the span and coordinate center, It appears that this has now been deprecated:
let span = MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)
let region = MKCoordinateRegion(center: coordinate, span: span)
MapView(region: region, mapType: mapType, coordinate: coordinate)
Here is my actual code for display a map (iOS 17):
struct DetailView: View {
var item: TravelEntries // <-- coordinates arrive from CoreData Transaction
@Binding var mapType: Int
var coordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: item.entryLat,
longitude: item.entryLong)
}
var selectedMapStyle: MapStyle {
return switch(mapType) {
case 0: .standard(elevation: .realistic)
case 1: .hybrid
case 2: .imagery
default: .standard(elevation: .realistic)
}
}
var body: some View {
VStack {
Map() {
Marker(item.entryCatName ?? "", coordinate: coordinate)
.tint(.red)
}
.mapStyle(selectedMapStyle)
ShowMapPicker(item: item, mapType: $mapType)
ShowDetails(item: item)
}
}
}
I have a travel app view with a list of category totals. If the user taps on any category in the list, the app then displays only the filtered entries assigned to the tapped category.
I am using GeometryReader to define the parent space, but I am unable to pass the GeometryReader status through the category filter filteredCategories() to the child space. The error I am seeing (Extra argument 'g' in call) is only occurring where filterCategories() is called.
I know that the arguments in the calling function must appear in the same order in the called function, but that seem to be working here. I have tried placement of GeometryReader like this:
filteredCategories(g: g, filter: categories.catItem[index].catName)
see #1 in filteredCategories()
and this:
filteredCategories(filter: categories.catItem[index].catName, g: g)
see #2 in filteredCategories()
In both case I get the same error Extra argument 'g' in call where filteredCategories() is called.
// display entries belonging to a single category
struct ShowCatEntries: View {
@EnvironmentObject var categories: Categories
var g: GeometryProxy
var index: Int
var body: some View {
VStack {
// fiter coreData
FilteredCategories(filter: categories.catItem[index].catName)
}.navigationTitle("\(categories.catItem[index].catName) Entries")
}
}
struct FilteredCategories: View {
//var g: GeometryProxy // #1
var fetchRequest: FetchRequest<CurrTrans>
init(filter: String) {
let predicate: NSPredicate? = NSPredicate(format: "entryCatName CONTAINS %@", filter)
fetchRequest = FetchRequest<CurrTrans>(sortDescriptors: [
NSSortDescriptor(keyPath: \CurrTrans.entryCatName, ascending: true)],
predicate: predicate)
}
//var g: GeometryProxy // #2
var body: some View {
List(fetchRequest.wrappedValue, id: \.self) { entry in
ShwProRow(g: g, entry: entry) // portrait
}.listStyle(PlainListStyle())
}
}
I would like to display transactions with date/time like the Apple Photos App. When a transaction takes place in, for example, Frankfurt (CET), or at a location in any other timezone on July 20 at 11:00 am, I want it to also display July 20 at 11:00 am in Los Angeles (PST).
I need help with saving off the timezone and how to restore the timezone for display the date. I would prefer to use date formatting starting with iOS 15
Below is what I am currently using to read and display the date. Saving a transaction date on July 20 at 11:00 am (CET) will display July 20 at 02:00 am instead of 11:00 am (PST).
DatePicker("", selection: $entryDT, in: ...Date())
let hDate = item.catDate
Text(hDate.formatted(.dateTime.day().month(.wide).year().hour().minute()))
I have been working on a travel app that records transactions. I was surprised recently to see the transaction entry date and time changing with travel across one or more time zone. For example, if I record a lunch expense in Frankfurt that occurred on May 12 at 18:05 (6:05 pm) Central European Summer Time. Then later upon return to the United States west coast (Pacific Daylight Time), I view the transaction in my travel app the transaction is displayed as occurring on May 12 at 09:05 in the morning.
I would like to maintain the original transaction date and time (May 12 18:05) no matter where the user happens to be located.
I'm guessing that I somehow need to save off the transaction timezone and apply that to the date / time upon display of the transaction. How do I do this?
I'm using DatePicker to get the current date:
DatePicker("", selection: $entryDT, in: ...Date())
.datePickerStyle(CompactDatePickerStyle())
I'm using the iOS 15 date formatting technique to display the date and time:
let hDate = item.entryDT ?? Date()
Text(hDate.formatted(.dateTime.day().month(.wide).year().hour().minute()))
I have an app that allows entry of transactions with location information that is saved to CoreData. Tapping on the History tab displays a list of transactions and tapping on a single transaction displays detailed transaction information with a map and abbreviated address that the user typed in such as Folsom, CA USA.
If the user would like to edit the location he may tap Edit location and enter a new address. Tapping Save returns to the transaction detail screen where the text location correctly changes to the new location but the map remains at the old location. Exiting the transaction detail screen and then selecting transaction detail again then shows the correct location on the map.
So I believe the map coordinates and text address are being correctly stored in CoreData. It appears that view update (refresh) is not occurring with the map in DetailView.
Below is the call from History to DetailView:
struct ShowHistoryRow: View {
var g: GeometryProxy
@ObservedObject var item: CurrTrans
var body: some View {
NavigationLink(destination: DetailView(item: item)) {
Start of detail view:
// Show transaction details
struct DetailView: View {
// var item: CurrTrans
@ObservedObject var item: CurrTrans // use to refresh view
var coordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: item.entryLat,
longitude: item.entryLong)
}
@State private var mapType: MKMapType = .standard
var body: some View {
GeometryReader { g in
// iPhone portrait mode?
if g.size.height > g.size.width {
PortDetailView(g: g, item: item, coordinate: coordinate, mapType: $mapType)
} else { // landscape
LandDetailView(g: g, item: item, coordinate: coordinate, mapType: $mapType)
}
}
}
call to Edit Location:
struct PortDetailView: View {
var g: GeometryProxy
var item: CurrTrans
var coordinate: CLLocationCoordinate2D
@Binding var mapType: MKMapType
var body: some View {
GeometryReader { g in
VStack {
// location services disabled?
if item.entryLat != 0.0 && item.entryLong != 0.0 {
ShowMap(item: item, coordinate: coordinate, mapType: mapType)
.frame(width: g.size.width, height: g.size.height * 0.68)
.padding(.bottom, 5)
Picker("", selection: $mapType) {
Text("Default").tag(MKMapType.standard)
Text("Transit").tag(MKMapType.hybrid)
Text("Satellite").tag(MKMapType.satellite)
}
.pickerStyle(SegmentedPickerStyle())
.font(.largeTitle)
} else {
VStack {
Text("Map Not Available")
.font(.title2)
.fontWeight(.bold)
Text("Location Services Disabled or Map Not Available")
.font(.subheadline)
}
}
ShowEntryDetails(g: g, item: item)
.padding(.bottom, g.size.height * -0.05)
NavigationLink(destination: EditLocation(g: g, item: item)) {Text("Edit Location")}
}
.font(.subheadline)
.navigationBarTitle("Transaction Details", displayMode: .inline)
.navigationViewStyle(StackNavigationViewStyle())
}
}
}
// Allow change in transaction location
struct EditLocation: View {
var g: GeometryProxy
var item: CurrTrans
@ObservedObject private var lm = LocationManager()
@Environment(\.dismiss) var dismiss
// persistant entry storage in coreData
@Environment(\.managedObjectContext) var viewContext
@State private var getStreet: String = ""
@State private var getCity: String = ""
@State private var getState: String = ""
@State private var getCountry: String = ""
@State private var invalidAddr: Bool = false
var body: some View {
VStack {
ShowEntryDetails(g: g, item: item)
.padding(.top, g.size.height > g.size.width ? g.size.height * 0.05 : 5)
ZStack {
GetFormEntry( getStreet: $getStreet, getCity: $getCity, getState: $getState, getCountry: $getCountry)
.frame(width: g.size.height > g.size.width ? g.size.width * 0.95: g.size.width * 0.75)
} .navigationBarTitle(Text("Edit Transaction Location"), displayMode: .inline) // end zstack
.navigationBarItems(trailing: Button(action: {
// prep address string for conversion to coordinates
let locStr = getStreet + "," + getCity + "," + getState + " " + getCountry
lm.getCoordinate(addressString: locStr) { coordinates, error in
print("edit coordiantes = \(coordinates)")
if error == nil {
print(coordinates.latitude)
print(coordinates.longitude)
item.entryLat = coordinates.latitude
item.entryLong = coordinates.longitude
//item.address = getStreet + "\n" + getCity + " " + getState + "\n" + getCountry
item.entryCity = getCity
item.entryState = getState
item.entryCountry = getCountry
do {
try viewContext.save()
} catch {
print(error.localizedDescription)
}
dismiss()
} else {
// Invalid address-- try again
invalidAddr = true
getStreet = ""
getCity = ""
getState = ""
getCountry = ""
}
}
}) {
Text ("Save")
}.disabled(getStreet.isEmpty || getCity.isEmpty || getCountry.isEmpty)
)
}
.alert("Invalid Address, Try Again", isPresented: $invalidAddr, actions: {
})
}
}
What information needs to be saved via UserDefaults as the result of a IAP transaction with a single non-consumable product? In the StoreKit 2 SKDemo it appears only consumable product purchases are saved via UserDefaults. It seems like for a non-consumable purchase I should be saving the purchaseDate, and the originalID or id.
What about saving the transaction.productID? Is this a unique identifier for every transaction?
I have a number of questions concerning the "Help with Purchases" view described in the Apple HIG here.
Would an In-App Purchase containing only non-consumable items require all input items (listed below in bold) on the "Help with Purchases view?"
Missing Purchase:
Is this information sent to Apple? If so, how? Can this be handled in App Store Connect by the developer?
Is this also used to install a purchased item on a second device such as purchase made on iPhone but want purchased items available on a iPad?
Here is what I found in the Apple SKDemo (storeKit 2):
try? await AppStore.sync()
whereas in storeKit 1:
store.restorePurchases()
Do these actually give the same results?
Frequently Asked Questions:
Are there some sample questions somewhere?
Request Refund:
Is there a list of codes for the five possible issues displayed? It looks like all the other information comes with the transaction / product ID. Are the Issues in a special form with a picker?
Submit Feedback:
Email address of developer?
Contact Us:
Is this an email address for Apple or the developer?
Thanks for your help.
I have an iPhone app that exports CSV files. I am getting the runtime error "[AXRuntimeCommon] Unknown client: " when the fileWrapper function in the MessageDocument structure is called. I have four different CSV exporters and they all call the same MessageDocument structure.
The CSV files look fine when dragged into Numbers. Does the exporter not like CSV formatted data? The contentType is set to .plainText
Unknown Client: Is some kind of data formatter missing?
Below I have listed one of my CSV file exporters and the MessageDocument structure.
struct CreateCsvTable: View {
var startDate: Date
var endDate: Date
@EnvironmentObject var base: BaseCurrency
@EnvironmentObject var categories: Categories
@EnvironmentObject var userData: UserData
@State private var showingExporter: Bool = false
@State private var document: MessageDocument?
var dates: String = ""
var body: some View {
let totalValue = userData.grandTotal
VStack {
Button ( action: {
self.showingExporter = true
let dates = userData.formatCsvDate(startDate: startDate, endDate: endDate)
let sCode = base.baseCur.baseS // 3 digit country code
document = categories.createCategoryTotalData(dates: dates, sCode: sCode, totalValue: totalValue)
}) {
HStack (alignment: .firstTextBaseline) {
Text("Export Category Totals")
.fontWeight(.bold)
.font(.title3)
Image(systemName: "square.and.arrow.up")
}
}
}.fileExporter(
isPresented: $showingExporter,
document: document,
contentType: .plainText,
defaultFilename: "TripSenseTotals.csv"
) { result in
switch result {
case .success(let url):
print("Saved to \(url)")
case .failure(let error):
print(error.localizedDescription)
}
}
.navigationBarTitle(Text("Export Category Totals"), displayMode: .inline)
}
}
struct MessageDocument: FileDocument {
static var readableContentTypes: [UTType] { [.plainText] }
var message: String = ""
init(message: String) {
self.message = message
}
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
message = string
}
// this will be called when the system wants to write our data to disk
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
return FileWrapper(regularFileWithContents: message.data(using: .utf8)!)
}
}
I have a SwiftUI app with a tab view within a navigation view. Changing the iPhone orientation (portrait to landscape or landscape back to portrait) while in a menu child view results with system control returning to the tab view menu rather than just redrawing the view. Re-orientating any of the tab views works as expected with a redraw of the view.
The sample code below is a complete example with a menu on tab E. Selecting for example "Edit option C" from the menu will take you to a view that states "This is option C". Changing the orientation of the iPhone results in return to the tab E menu view with the menu squashed in the left half of the screen rather than displaying "This is option C" in the new orientation.
Am I missing something or is this "normal" Swift behavior?
enum Tabs: String {
case home
case tabb
case tabc
case tabd
case tabe
}
struct ContentView: View {
@State private var selectedTab: Tabs = .home
var body: some View {
NavigationView {
TabView (selection: $selectedTab) {
TabAView()
.tabItem {
Label("Tab A", systemImage: "house.circle.fill")
}.tag(Tabs.home)
TabBView()
.tabItem {
Label("Tab B", systemImage: "b.circle.fill")
}.tag(Tabs.tabb)
TabCView()
.tabItem {
Label("Tab C", systemImage: "c.circle.fill")
}.tag(Tabs.tabc)
TabDView()
.tabItem {
Label("Tab D", systemImage: "d.circle.fill")
}.tag(Tabs.tabd)
TabEView()
.tabItem {
Label("Tab E", systemImage: "e.circle.fill")
}.tag(Tabs.tabe)
}
}.accentColor(.green)
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct TabAView: View {
var body: some View {
Text("This is tab A")
}
}
struct TabBView: View {
var body: some View {
Text("This is tab B")
}
}
struct TabCView: View {
var body: some View {
Text("This is tab C")
}
}
struct TabDView: View {
var body: some View {
Text("This is tab D")
}
}
struct TabEView: View {
var body: some View {
GeometryReader { g in
VStack (alignment: .leading) {
Text("Tab E")
.font(.title2)
.fontWeight(.bold)
.padding(.leading, 20)
List {
NavigationLink(destination: AView()) {Text("Edit Option A")}
NavigationLink(destination: BView()) {Text("Edit Option B")}
NavigationLink(destination: CView()) {Text("Edit Option C")}
NavigationLink(destination: DView()) {Text("Edit Option D")}
}
.font(.body)
.environment(\.defaultMinListRowHeight, 12)
.padding(.leading, 25)
}
}
}
}
struct AView: View {
var body: some View {
Text("This is option A")
}
}
struct BView: View {
var body: some View {
Text("This is option B")
}
}
struct CView: View {
var body: some View {
Text("This is option C")
}
}
struct DView: View {
var body: some View {
Text("This is option D")
}
}