Skip to content

Instantly share code, notes, and snippets.

@bacongravy
Created March 6, 2023 06:23
Show Gist options
  • Save bacongravy/92a0822e06338599fe12ea6dca3b84a8 to your computer and use it in GitHub Desktop.
Save bacongravy/92a0822e06338599fe12ea6dca3b84a8 to your computer and use it in GitHub Desktop.
struct InjectedNSView<Content: View, Injected: NSView>: NSViewRepresentable {
let content: Content
let injected: () -> Injected
func makeNSView(context: Context) -> Injected {
let injected = injected()
let hostingView = NSHostingView(rootView: content)
hostingView.translatesAutoresizingMaskIntoConstraints = false
injected.addSubview(hostingView)
hostingView.topAnchor.constraint(equalTo: injected.topAnchor).isActive = true
hostingView.bottomAnchor.constraint(equalTo: injected.bottomAnchor).isActive = true
hostingView.leftAnchor.constraint(equalTo: injected.leftAnchor).isActive = true
hostingView.rightAnchor.constraint(equalTo: injected.rightAnchor).isActive = true
return injected
}
func updateNSView(_ nsView: Injected, context: Context) {
if let view = nsView.subviews.first as? NSHostingView<Content> {
view.rootView = content
}
}
}
// Example usage
extension View {
func onKeyDown(perform action: @escaping OnKeyDown.Action) -> some View {
return InjectedNSView(content: self) { OnKeyDown(action: action) }
}
}
class OnKeyDown: EventHandlerView {
override func keyDown(with event: NSEvent) {
if !action(event) {
super.keyDown(with: event)
}
}
}
class EventHandlerView: NSView {
typealias Action = (NSEvent) -> Bool
let action: Action
init(action: @escaping Action) {
self.action = action
super.init(frame: .zero)
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private let characterSet = CharacterSet.init()
.union(.alphanumerics)
.union(.punctuationCharacters)
.union(.symbols)
.union(.whitespacesAndNewlines)
.subtracting(.init(charactersIn: "\t"))
struct BasicTextInput: View {
@Binding var text: String
var body: some View {
ZStack(alignment: .topLeading) {
RoundedRectangle(cornerRadius: 8.0)
.strokeBorder(style: .init(lineWidth: 2))
.frame(maxWidth: .infinity, maxHeight: .infinity)
Text(text)
.padding(8)
}
.padding(.horizontal, -0.5)
.padding(.bottom, -0.25)
.contentShape(RoundedRectangle(cornerRadius: 7.0))
.focusable()
.padding(2)
// *******************************************************
.onKeyDown { event in
if let specialKey = event.specialKey,
specialKey == .delete,
text.count > 0 {
let index = text.index(text.endIndex, offsetBy: -1)
text = String(text.prefix(upTo: index))
return true
}
else if let characters = event.characters,
let unicodeScalar = characters.unicodeScalars.first {
if characterSet.contains(unicodeScalar) {
text.append(characters)
return true
}
}
return false
}
// *******************************************************
.padding(-2)
}
}
struct BasicTextInput_PreviewContainer: View {
@State private var text: String = ""
var body: some View {
VStack {
Spacer()
Text("Enter some text:").font(.title).bold()
.frame(maxWidth: .infinity, alignment: .leading)
BasicTextInput(text: $text)
Button("Clear") {
text = ""
}
.frame(maxWidth: .infinity, alignment: .trailing)
Spacer()
}
.padding()
}
}
struct BasicTextInput_Previews: PreviewProvider {
static var previews: some View {
BasicTextInput_PreviewContainer()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment