Skip to content

Instantly share code, notes, and snippets.

@diogoos
Created December 21, 2020 16:12
Show Gist options
  • Select an option

  • Save diogoos/5624236ed1fb38eb334c48e50d79553a to your computer and use it in GitHub Desktop.

Select an option

Save diogoos/5624236ed1fb38eb334c48e50d79553a to your computer and use it in GitHub Desktop.
MacOS SwiftUI textfield that autoresizes to fit the content (like iMessage bubble)
//
// DynamicTextView.swift
// Originally for "Issuing"
//
// Created by Diogo Silva on 12/21/20.
//
import AppKit
import SwiftUI
fileprivate struct DynamicTextViewWrapper: NSViewRepresentable {
@Binding var text: String
var onResize: (CGSize) -> ()
private let view = NSTextView()
func makeNSView(context: Context) -> NSTextView {
view.backgroundColor = .textBackgroundColor
view.font = .systemFont(ofSize: 15)
view.isEditable = context.environment.isEnabled
view.delegate = context.coordinator
view.layoutManager?.delegate = context.coordinator
return view
}
func updateNSView(_ nsView: NSTextView, context: Context) {
view.string = text
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, NSLayoutManagerDelegate, NSTextViewDelegate {
func textDidChange(_ notification: Notification) {
parent.text = parent.view.string
}
var parent: DynamicTextViewWrapper
init(_ parent: DynamicTextViewWrapper) {
self.parent = parent
}
func layoutManager(_ layoutManager: NSLayoutManager, textContainer: NSTextContainer, didChangeGeometryFrom oldSize: NSSize) {
layoutManager.ensureLayout(for: textContainer)
parent.onResize(layoutManager.usedRect(for: textContainer).size)
}
func layoutManager(_ layoutManager: NSLayoutManager, didCompleteLayoutFor textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) {
if let textContainer = textContainer {
parent.onResize(layoutManager.usedRect(for: textContainer).size)
}
}
}
}
struct DynamicTextView: View {
@Binding var text: String
@State private var textViewSize: CGSize = .zero
var body: some View {
VStack {
DynamicTextViewWrapper(text: $text, onResize: { newSize in
textViewSize = newSize
})
.frame(height: textViewSize.height)
.fixedSize(horizontal: false, vertical: true)
.background(Color(NSColor.windowBackgroundColor)) // hides background flashes during quick view changes
}
.padding(.vertical, 5)
.background(Color(NSColor.textBackgroundColor))
}
}
@diogoos
Copy link
Copy Markdown
Author

diogoos commented Dec 21, 2020

Here's what the end result could look like:

dynamictextviewdemo3x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment