-
Star
(181)
You must be signed in to star a gist -
Fork
(13)
You must be signed in to fork a gist
-
-
Save unnamedd/6e8c3fbc806b8deb60fa65d6b9affab0 to your computer and use it in GitHub Desktop.
/** | |
* MacEditorTextView | |
* Copyright (c) Thiago Holanda 2020-2021 | |
* https://bsky.app/profile/tholanda.com | |
* | |
* (the twitter account is now deleted, please, do not try to reach me there) | |
* https://twitter.com/tholanda | |
* | |
* MIT license | |
*/ | |
import Combine | |
import SwiftUI | |
struct MacEditorTextView: NSViewRepresentable { | |
@Binding var text: String | |
var isEditable: Bool = true | |
var font: NSFont? = .systemFont(ofSize: 14, weight: .regular) | |
var onEditingChanged: () -> Void = {} | |
var onCommit : () -> Void = {} | |
var onTextChange : (String) -> Void = { _ in } | |
func makeCoordinator() -> Coordinator { | |
Coordinator(self) | |
} | |
func makeNSView(context: Context) -> CustomTextView { | |
let textView = CustomTextView( | |
text: text, | |
isEditable: isEditable, | |
font: font | |
) | |
textView.delegate = context.coordinator | |
return textView | |
} | |
func updateNSView(_ view: CustomTextView, context: Context) { | |
view.text = text | |
view.selectedRanges = context.coordinator.selectedRanges | |
} | |
} | |
// MARK: - Preview | |
#if DEBUG | |
struct MacEditorTextView_Previews: PreviewProvider { | |
static var previews: some View { | |
Group { | |
MacEditorTextView( | |
text: .constant("{ \n planets { \n name \n }\n}"), | |
isEditable: true, | |
font: .userFixedPitchFont(ofSize: 14) | |
) | |
.environment(\.colorScheme, .dark) | |
.previewDisplayName("Dark Mode") | |
MacEditorTextView( | |
text: .constant("{ \n planets { \n name \n }\n}"), | |
isEditable: false | |
) | |
.environment(\.colorScheme, .light) | |
.previewDisplayName("Light Mode") | |
} | |
} | |
} | |
#endif | |
// MARK: - Coordinator | |
extension MacEditorTextView { | |
class Coordinator: NSObject, NSTextViewDelegate { | |
var parent: MacEditorTextView | |
var selectedRanges: [NSValue] = [] | |
init(_ parent: MacEditorTextView) { | |
self.parent = parent | |
} | |
func textDidBeginEditing(_ notification: Notification) { | |
guard let textView = notification.object as? NSTextView else { | |
return | |
} | |
self.parent.text = textView.string | |
self.parent.onEditingChanged() | |
} | |
func textDidChange(_ notification: Notification) { | |
guard let textView = notification.object as? NSTextView else { | |
return | |
} | |
self.parent.text = textView.string | |
self.selectedRanges = textView.selectedRanges | |
} | |
func textDidEndEditing(_ notification: Notification) { | |
guard let textView = notification.object as? NSTextView else { | |
return | |
} | |
self.parent.text = textView.string | |
self.parent.onCommit() | |
} | |
} | |
} | |
// MARK: - CustomTextView | |
final class CustomTextView: NSView { | |
private var isEditable: Bool | |
private var font: NSFont? | |
weak var delegate: NSTextViewDelegate? | |
var text: String { | |
didSet { | |
textView.string = text | |
} | |
} | |
var selectedRanges: [NSValue] = [] { | |
didSet { | |
guard selectedRanges.count > 0 else { | |
return | |
} | |
textView.selectedRanges = selectedRanges | |
} | |
} | |
private lazy var scrollView: NSScrollView = { | |
let scrollView = NSScrollView() | |
scrollView.drawsBackground = true | |
scrollView.borderType = .noBorder | |
scrollView.hasVerticalScroller = true | |
scrollView.hasHorizontalRuler = false | |
scrollView.autoresizingMask = [.width, .height] | |
scrollView.translatesAutoresizingMaskIntoConstraints = false | |
return scrollView | |
}() | |
private lazy var textView: NSTextView = { | |
let contentSize = scrollView.contentSize | |
let textStorage = NSTextStorage() | |
let layoutManager = NSLayoutManager() | |
textStorage.addLayoutManager(layoutManager) | |
let textContainer = NSTextContainer(containerSize: scrollView.frame.size) | |
textContainer.widthTracksTextView = true | |
textContainer.containerSize = NSSize( | |
width: contentSize.width, | |
height: CGFloat.greatestFiniteMagnitude | |
) | |
layoutManager.addTextContainer(textContainer) | |
let textView = NSTextView(frame: .zero, textContainer: textContainer) | |
textView.autoresizingMask = .width | |
textView.backgroundColor = NSColor.textBackgroundColor | |
textView.delegate = self.delegate | |
textView.drawsBackground = true | |
textView.font = self.font | |
textView.isEditable = self.isEditable | |
textView.isHorizontallyResizable = false | |
textView.isVerticallyResizable = true | |
textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) | |
textView.minSize = NSSize(width: 0, height: contentSize.height) | |
textView.textColor = NSColor.labelColor | |
textView.allowsUndo = true | |
return textView | |
}() | |
// MARK: - Init | |
init(text: String, isEditable: Bool, font: NSFont?) { | |
self.font = font | |
self.isEditable = isEditable | |
self.text = text | |
super.init(frame: .zero) | |
} | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
// MARK: - Life cycle | |
override func viewWillDraw() { | |
super.viewWillDraw() | |
setupScrollViewConstraints() | |
setupTextView() | |
} | |
func setupScrollViewConstraints() { | |
scrollView.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(scrollView) | |
NSLayoutConstraint.activate([ | |
scrollView.topAnchor.constraint(equalTo: topAnchor), | |
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), | |
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor), | |
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor) | |
]) | |
} | |
func setupTextView() { | |
scrollView.documentView = textView | |
} | |
} |
/** | |
* MacEditorTextView | |
* Copyright (c) Thiago Holanda 2020-2021 | |
* https://bsky.app/profile/tholanda.com | |
* | |
* (the twitter account is now deleted, please, do not try to reach me there) | |
* https://twitter.com/tholanda | |
* | |
* MIT license | |
*/ | |
import SwiftUI | |
import Combine | |
struct ContentQueryView: View { | |
@State private var queryText = "{ \n planets { \n name \n }\n}" | |
@State private var responseJSONText = "{ \"name\": \"Earth\"}" | |
var body: some View { | |
let queryTextView = MacEditorTextView( | |
text: $queryText, | |
isEditable: false, | |
font: .systemFont(ofSize: 14, weight: .regular) | |
) | |
.frame(minWidth: 300, | |
maxWidth: .infinity, | |
minHeight: 300, | |
maxHeight: .infinity) | |
let responseTextView = MacEditorTextView( | |
text: $responseJSONText, | |
isEditable: false, | |
font: .userFixedPitchFont(ofSize: 14) | |
) | |
.frame(minWidth: 300, | |
maxWidth: .infinity, | |
minHeight: 300, | |
maxHeight: .infinity) | |
return HSplitView { | |
queryTextView | |
responseTextView | |
} | |
} | |
} |
@unnamedd Find bar is not working, Could you please help fixing it.
I added below line of code for NSTextView.
textView.usesFindBar = true
textView.usesFindPanel = false
textView.isIncrementalSearchingEnabled = true
This lines works when i add textview using storyboard but not working when using this for SwiftUI
Hi @RizwanaDesai,
unfortunately I don't see why it is not working for you, in any case, I am working with a friend to create a package of this humble gist in order to make it better and easier to use.
I will give a check on the problem you've reported, but you can do that too, since the code is all exposed here and there's no hidden magic being done. I would of course highly appreciate if you post here a possible solution you may find (in case you find a fix before me).
Sure @unnamedd .
Thank you for this! I loved the simplicity of yours!
I have combined your implementation with the MacEditorTextView for the extra functions, along with a couple of other changes. One of them being the ability to add a placeholder text to the scrollview, and another being a working onSubmit function.
It is found here in case anyone can find the alterations beneficial.