Here is my code:

import SwiftUI


struct TestTextView: View {
    
    @State var title: String = ""
 
    
    var body: some View {
        GeometryReader { geo in
            ScrollView {
                VStack(spacing: 16) {
                    TextField(text: $title) {
                        Text("Title")
                    }
                    
                    RichTestTextView(attributedString: NSAttributedString(string: "Here is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is \n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new lineinstead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screen ------end"),  fixedWidth: geo.size.width-32)
                        .frame(width: geo.size.width-32)
                        .frame(minHeight: 100)
                    
                    Color.pink
                        .frame(height: 100)
                        .cornerRadius(12)
                }
                .padding(.vertical, 24)
                .padding(.horizontal, 16)
            }
        }
    }
}

  
struct RichTestTextView: UIViewRepresentable {
    var attributedString: NSAttributedString
    var fixedWidth: CGFloat
    
    let textView = UITextView(frame: .zero)
     
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    
    
    class Coordinator: NSObject, UITextViewDelegate {
        var parent: RichTestTextView
        init(_ parent: RichTestTextView) {
            self.parent = parent
        }
        
        func textViewDidChange(_ textView: UITextView) {
            textView.scrollToBottom()
        }
    }
    
    func makeUIView(context: Context) -> UITextView {
        textView.isEditable = true
        textView.isScrollEnabled = false
        textView.backgroundColor = .cyan // just to make it easier to see
        textView.delegate = context.coordinator
        textView.sizeToFit()
        
        textView.attributedText = self.attributedString

        textView.translatesAutoresizingMaskIntoConstraints = false
        
        textView.widthAnchor.constraint(equalToConstant: fixedWidth).isActive = true
        
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
 
    }
}

extension UITextView {
    func scrollToBottom() {
        if !self.text.isEmpty {
            let range = NSMakeRange(self.text.count - 1, 1)
            self.scrollRangeToVisible(range)
        }
    }
}

#Preview {
    TestTextView()
}

My Goal: Some views, including UITextView, and others as SwiftUI Views, should scroll and allow typing seamlessly within a SwiftUI ScrollView.

The key point is that I prefer not to use UIKit code to achieve my goal, as it is neither elegant nor time-efficient, even though I already have existing UIKit code that accomplishes the desired effect.

I am currently developing a diary app, and this presents a challenge for me when it comes to editing diary content.

I want to type and scroll normally in UITextView and ScrollView

Question update: 2023-11-03

I sovled this problem!!! The key method is updateContentSize(textView) in textViewDidChange(_ textView: UITextView)

struct RichTextView: UIViewRepresentable {
    
    var text: NSAttributedString
    
    var isEditable: Bool = true

    var width: CGFloat
        
    var onTextChanged: ((_ textView: UITextView) -> Void)?
    
    private let textView = UITextView(frame: .zero)
     
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    
    class Coordinator: NSObject, UITextViewDelegate {
        var parent: RichTextView
     
        init(_ parent: RichTextView) {
            self.parent = parent
        }
        
        func textViewDidChange(_ textView: UITextView) {
            DispatchQueue.main.async {
                self.parent.onTextChanged?(textView)
            }
            updateContentSize(textView)
        }
        
        func updateContentSize(_ textView: UITextView) {
            let size = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: .infinity))
            if textView.frame.size != size {
                textView.frame.size = size
            }
        }
    }
    
    func makeUIView(context: Context) -> UITextView {
        textView.isEditable = isEditable
        textView.isScrollEnabled = false
        textView.backgroundColor = .clear
        textView.dataDetectorTypes = .all
        textView.delegate = context.coordinator
        textView.sizeToFit()
        
        textView.attributedText = self.text

        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.widthAnchor.constraint(equalToConstant: width).isActive = true
        
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
 
    }
}


struct TestTextView: View {
    
    @State var title: String = ""
 
    
    var body: some View {
        GeometryReader { geo in
            ScrollView {
                VStack(spacing: 16) {
                    TextField(text: $title) {
                        Text("Title")
                    }
                    
                    RichTextView(text: NSAttributedString(string: "Here is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is \n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new line\n1new lineinstead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screenHere is a long string as you can see it is not wrapping but instead scrolling off the edge of the screen ------end"),  width: geo.size.width-32)
                        .frame(width: geo.size.width-32)
                        .frame(minHeight: 100)
                    
                    Color.pink
                        .frame(height: 100)
                        .cornerRadius(12)
                }
                .padding(.vertical, 24)
                .padding(.horizontal, 16)
            }
        }
    }
}

#Preview {
    TestTextView()
}

KeyboardAdaptive.swift to solve The cusor did not change followling my typing line if TestTextView is in a NavigationStack or NavigationLink

import SwiftUI
import NotificationCenter
import Combine


extension Notification {
    var keyboardHeight: CGFloat {
        return (userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0
    }
}

extension Publishers {
     
    static var keyboardHeight: AnyPublisher<CGFloat, Never> {
         
        let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
            .map { $0.keyboardHeight }
        
        let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
            .map { _ in CGFloat(0) }
         
        return MergeMany(willShow, willHide)
            .eraseToAnyPublisher()
    }
}


struct KeyboardAdaptive: ViewModifier {
    @State private var keyboardHeight: CGFloat = 0

    func body(content: Content) -> some View {
        content
            .padding(.bottom, keyboardHeight)
            .onReceive(Publishers.keyboardHeight) { self.keyboardHeight = $0 }
    }
}

extension View {
    func keyboardAdaptive() -> some View {
        ModifiedContent(content: self, modifier: KeyboardAdaptive())
    }
}

0

There are 0 best solutions below