Originally I just passed a label to the actual TextField but could not get the desired effect.
This is the solution I'm using but I find it unsatisfactory. I wrap each form item in LabeledContent. It does not look as tidy as the Form label formatting but without it when I use .alignmentGuide it operates from a unique origin which depends on the label but simply adjusting for the label length does not give consistent results.
Form {
LabeledContent {
CustomField(
thing: $activeThing,
setValue: { thingtype, newValue in thingtype.thing = newValue }
)
} label: {
Text(shortLabel)
.frame(width: longLabelWidth, alignment: .trailing)
}
CustomField(
thing: $activeThing,
setValue: { thingtype, newValue in thingtype.thing = newValue }
)
} label: {
Text(longLabel)
.frame(width: longLabelWidth, alignment: .trailing)
}
}
struct CustomField: View {
...
var body: some View {
TextField("", text: $displayText)
.inlineCompletion(
text: displayText,
completion: completion,
)
.onAppear {displayText = getValue(transaction)}
struct InlineCompletion: ViewModifier {
let text: String
let completion: String
func body(content: Content) -> some View {
ZStack(alignment: .leading) {
content
if !completion.isEmpty {
Text(completion)
.foregroundColor(.secondary)
.opacity(text.isEmpty ? 0 : 0.7)
.alignmentGuide(.leading) { _ in
-textWidth(text) - baseFontSize
}
}
}
}
}
extension View {
func inlineCompletion(text: String, completion: String) -> some View {
modifier(
InlineCompletion(
text: text,
completion: completion
)
)
}
}
Post
Replies
Boosts
Views
Activity
Thanks for making that clear.
For the sake of helping anyone else with this issue, my solution is to use a pair of functions which cleans the typed expression as best it can but then calmly handles errors on any remaining malformed input
private func handleSubmit() {
print("handling submit")
if let result = safelyEvaluateExpression() {
inputExpression = result
isError = false
} else {
isError = true
}
}
private func safelyEvaluateExpression() -> String? {
let preparedExpression = inputExpression
.replacingOccurrences(of: "×", with: "*")
.replacingOccurrences(of: "÷", with: "/")
.trimmingCharacters(in: .whitespacesAndNewlines)
guard !preparedExpression.isEmpty else { return nil }
let allowedChars = CharacterSet(charactersIn: "0123456789.+-*/() ")
guard preparedExpression.unicodeScalars.allSatisfy({ allowedChars.contains($0) }) else {
return nil
}
if let result = NSExpressionWrapper.evaluateExpression(preparedExpression) {
return String(format: "%.2f", result.doubleValue)
}
return nil
}
with:
// NSExpressionWrapper.h
#import <Foundation/Foundation.h>
@interface NSExpressionWrapper : NSObject
+ (NSNumber *)evaluateExpression:(NSString *)string;
@end
and
// NSExpressionWrapper.m
#import "NSExpressionWrapper.h"
@implementation NSExpressionWrapper
+ (NSNumber *)evaluateExpression:(NSString *)string {
@try {
NSExpression *expression = [NSExpression expressionWithFormat:string];
return [expression expressionValueWithObject:nil context:nil];
} @catch (NSException *exception) {
// Log if needed
NSLog(@"Caught exception: %@", exception.reason);
return nil; // Return nil on error
}
}
@end
In the SwiftUI code I use isError to overlay the TextField to let the user know they have work to do.
Claude helped me with this code. I am not a programmer.
I'm disappointed that I need to use Obj-C code and not stick to Swift in order to allow simple inline math but perhaps with the advent of math in apps like Notes there will be a Swift solution in the future.
I added a .sheet in Main where I can access the shared modelContext and all is well.
I'd still appreciate answers to #3
I've not gotten anywhere with this. The issue is how does one abstract code from within a DisclosureTableRow and return either another DisclosureTableRow or a TableRow. Why not organize the accounts hierarchically and feed that to Table? Because then childless parents have disclosure triangles and there are issues with expansion.
My solution, which I learned from elsewhere on this forum, is just to drag the unique name property of my object as a String and then in the action closure use the name to find the dropped object.
Interestingly, when I try to abstract this modifier the compiler expects the elements that prevent compilation; perhaps this would work with something more specific than some View.
struct DropableTableRowModifier: ViewModifier {
let object: Object:
func body(content: Content) -> some View {
content
.dropDestination(for: String.self) { objectNames in
// do things with object and objectNames
return
}
I had read that the answer was to mark the shared object with @Binding instead of @State but that was not sufficient. See https://developer.apple.com/forums/thread/735416?answerId=761483022#761483022
In my case I needed to add the equivalent of
@Bindable var profile = profile // must be placed in view body