Skip to content

Commit

Permalink
Auto growing code editor
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-magda committed Sep 27, 2023
1 parent 33ac6f5 commit 3bd2a9e
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ struct CodeEditor: UIViewRepresentable {

var onDidEndEditing: (() -> Void)?

var onDidChangeHeight: ((CGFloat) -> Void)?

// MARK: UIViewRepresentable

static func dismantleUIView(_ uiView: CodeEditorView, coordinator: Coordinator) {
Expand All @@ -36,6 +38,7 @@ struct CodeEditor: UIViewRepresentable {
coordinator.onCodeDidChange = nil
coordinator.onDidBeginEditing = nil
coordinator.onDidEndEditing = nil
coordinator.onDidChangeHeight = nil
coordinator.suggestionsPresentationContextProvider = nil
}

Expand Down Expand Up @@ -89,6 +92,7 @@ struct CodeEditor: UIViewRepresentable {

onDidEndEditing?()
}
context.coordinator.onDidChangeHeight = onDidChangeHeight
}
}

Expand All @@ -104,6 +108,8 @@ extension CodeEditor {

var onDidEndEditing: (() -> Void)?

var onDidChangeHeight: ((CGFloat) -> Void)?

init(suggestionsPresentationContextProvider: CodeEditorSuggestionsPresentationContextProviding?) {
self.suggestionsPresentationContextProvider = suggestionsPresentationContextProvider
}
Expand All @@ -127,6 +133,10 @@ extension CodeEditor {
) -> UIViewController? {
suggestionsPresentationContextProvider?.presentationController(for: codeEditorView)
}

func codeEditorViewDidChangeHeight(_ codeEditorView: CodeEditorView, height: CGFloat) {
onDidChangeHeight?(height)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,13 @@ extension CodeEditorView: ProgrammaticallyInitializableViewProtocol {
}
}

// MARK: - CodeEditorView: UITextViewDelegate -
// MARK: - CodeEditorView: CodeTextViewDelegate -

extension CodeEditorView: CodeTextViewDelegate {
func codeTextViewDidChangeHeight(_ textView: CodeTextView, height: CGFloat) {
delegate?.codeEditorViewDidChangeHeight(self, height: height)
}

extension CodeEditorView: UITextViewDelegate {
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
delegate?.codeEditorView(self, beginEditing: isEditable)
return isEditable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ protocol CodeEditorViewDelegate: AnyObject {
func codeEditorViewDidBeginEditing(_ codeEditorView: CodeEditorView)
func codeEditorViewDidEndEditing(_ codeEditorView: CodeEditorView)
func codeEditorViewDidRequestSuggestionPresentationController(_ codeEditorView: CodeEditorView) -> UIViewController?
func codeEditorViewDidChangeHeight(_ codeEditorView: CodeEditorView, height: CGFloat)
}

extension CodeEditorViewDelegate {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Highlightr
import UIKit

protocol CodeTextViewDelegate: UITextViewDelegate {
func codeTextViewDidChangeHeight(_ textView: CodeTextView, height: CGFloat)
}

extension CodeTextView {
struct Appearance {
var gutterWidth: CGFloat = 24
Expand Down Expand Up @@ -39,6 +43,10 @@ final class CodeTextView: UITextView {
private lazy var codeTextViewLayoutManager = layoutManager as? CodeTextViewLayoutManager
private lazy var codeAttributedString = textStorage as? CodeAttributedString

// Calculate textview's height
private var oldText: String = ""
private var oldSize: CGSize = .zero

var language: String? {
didSet {
guard language != oldValue,
Expand Down Expand Up @@ -121,6 +129,7 @@ final class CodeTextView: UITextView {
override func layoutSubviews() {
super.layoutSubviews()
codeTextViewLayoutManager?.appearance.currentLineWidth = bounds.width
calculateBestFitsSize()
}

override func draw(_ rect: CGRect) {
Expand Down Expand Up @@ -231,6 +240,23 @@ final class CodeTextView: UITextView {

return UIColor(red: 1.0 - r, green: 1.0 - g, blue: 1.0 - b, alpha: 1)
}

private func calculateBestFitsSize() {
guard bounds.size.width > 0,
let delegate = delegate as? CodeTextViewDelegate else {
return
}

if text == oldText && bounds.size == oldSize {
return
}

oldText = text
oldSize = bounds.size

let size = sizeThatFits(CGSize(width: bounds.size.width, height: CGFloat.greatestFiniteMagnitude))
delegate.codeTextViewDidChangeHeight(self, height: size.height)
}
}

// MARK: - CodeTextView: NSLayoutManagerDelegate -
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ final class StepAssembly: Assembly, UIKitAssembly {
panModalPresenter: PanModalPresenter()
)
let hostingController = StyledHostingController(rootView: stepView, appearance: .withoutBackButtonTitle)
hostingController.hidesBottomBarWhenPushed = true
// Fixes an issue with that SwiftUI view content layout unexpectedly pop/jumps on appear
hostingController.navigationItem.largeTitleDisplayMode = .never

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI
extension StepQuizCodeEditorView {
struct Appearance {
let codeEditorInsets = LayoutInsets(vertical: LayoutInsets.defaultInset)
let codeEditorMinHeightHeight: CGFloat = 300
let codeEditorMinHeight: CGFloat = 300
}
}

Expand All @@ -19,6 +19,8 @@ struct StepQuizCodeEditorView: View {

@Environment(\.isEnabled) private var isEnabled

@State private var height: CGFloat = 300

var body: some View {
VStack(spacing: 0) {
Divider()
Expand Down Expand Up @@ -59,12 +61,21 @@ struct StepQuizCodeEditorView: View {
codeTemplate: codeTemplate,
language: language,
isEditable: true,
textInsets: appearance.codeEditorInsets.uiEdgeInsets
)
.frame(
maxWidth: .infinity,
minHeight: appearance.codeEditorMinHeightHeight
textInsets: appearance.codeEditorInsets.uiEdgeInsets,
onDidChangeHeight: { newHeight in
let constrainMinimumHeight = max(newHeight, appearance.codeEditorMinHeight)
guard constrainMinimumHeight != height else {
return
}

DispatchQueue.main.async {
height = constrainMinimumHeight
KeyboardManager.reloadLayoutIfNeeded()
}
}
)
.frame(height: height)
.frame(maxWidth: .infinity)

Divider()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ enum KeyboardManager {
static func setEnabled(_ isEnabled: Bool) {
IQKeyboardManager.shared.enable = isEnabled
}

static func reloadLayoutIfNeeded() {
IQKeyboardManager.shared.reloadLayoutIfNeeded()
}
}

0 comments on commit 3bd2a9e

Please sign in to comment.