This solution is adapted from responses (primarily Mojtaba Hosseini) to a couple of similar questions on StackOverflow and also from Alan Quatermain's well written blog titled "SwiftUI Bindings with CoreData".
The cross platform appearance is not identical and the need to add the hacks is annoying, but it works.
I use this solution for both macOS and iOS targets (with Core Data) and an
@ObservedObject var. I've adapted this answer to work independently with an
@State var. This builds and runs in both targets but some of the view characteristics are "out-of-sync".
Essentially
TextEditor and
Text views must be drawn in the same way - must have identical view modifier characteristics, e.g.
.font modifier, etc. The
Text view "ghosts" the
TextEditor and performs two roles:
presents the placeholder text when the Optional<String> (@State private var objectDescription: String?) is nil.
dynamically expands the height of the stacked views to accommodate multiple lines of text in the TextEditor.
If anyone understands the mechanics behind what makes the "ghosting" technique work in the
ZStack, please add an answer, because I'm still trying to figure that out.
Code Block struct TextEditorView: View { |
@State private var objectDescription: String? |
var body: some View { |
VStack(alignment: .leading) { |
let placeholder = "enter detailed Description" |
Text("Description") |
ZStack(alignment: .topLeading) { |
TextEditor(text: Binding($objectDescription, replacingNilWith: "")) |
.frame(minHeight: 30, alignment: .leading) |
// following line is a hack to force TextEditor to appear |
// similar to .textFieldStyle(RoundedBorderTextFieldStyle())... |
.cornerRadius(6.0) |
.foregroundColor(Color(.labelColor)) |
.multilineTextAlignment(.leading) |
Text(objectDescription ?? placeholder) |
// following line is a hack to create an inset similar to the TextEditor inset... |
.padding(.leading, 4) |
.foregroundColor(Color(.placeholderTextColor)) |
.opacity(objectDescription == nil ? 1 : 0) |
} |
.font(.body) // if you'd prefer the font to appear the same for both iOS and macOS |
} |
} |
} |
// Full credit for this extension to
Binding to Alan Quatermain.
Code Block public extension Binding where Value: Equatable { |
|
init(_ source: Binding<Value?>, replacingNilWith nilProxy: Value) { |
self.init( |
get: { source.wrappedValue ?? nilProxy }, |
set: { newValue in |
if newValue == nilProxy { |
source.wrappedValue = nil |
} |
else { |
source.wrappedValue = newValue |
} |
}) |
} |