관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] iOS16+ UITextView 입력 중 위로 스크롤 되는 버그 해결 방법 (최신) (UIScrollView, UIStackView, UITextView) 본문

iOS 응용 (swift)

[iOS - swift] iOS16+ UITextView 입력 중 위로 스크롤 되는 버그 해결 방법 (최신) (UIScrollView, UIStackView, UITextView)

jake-kim 2023. 11. 18. 01:19

UITextView 입력 중 위로 스크롤 되는 현상

  • UIStackView를 사용하여 리스트 형태의 뷰를 보여줄 때 중간에 UITextView가 있다면 버그가 발생
    • 뷰형태: UIScrollView안에 UIStackView을 넣고, 이 스택뷰에 UITextView 넣어서 리스트 형태의 뷰를 만든 형태
    • (UITextView의 isScrollEnabled는 false로 설정)

ex) 뷰 형태

import UIKit

class ViewController: UIViewController {
    private let scrollView = UIScrollView()
    private let stackView = UIScrollView()
    private let textView = UITextView()
    private let button = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(scrollView)
        scrollView.addSubview(stackView)
        stackView.addArrangedSubview(textView)
        stackView.addArrangedSubview(button)
        
        NSLayoutConstraint.activate([
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
        ])
        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            stackView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width)
        ])
    }
}

iOS16부터 생겨난 버그 - 스크롤이 위로 튕기는 현상

  • 1) 텍스트 하단 우측에서 텍스트를 입력할 때 다음줄로 넘어가는 케이스

  • 2) 이모지 왼쪽에 문자 입력하는 케이스

  • 3) 이모지 왼쪽에 커서두고 키보드에서 한영 <-> 이모지 변경하는 케이스

해결 방법

  • UITextView를 서브 클래싱하여 SelfSizingTextView 구현
    • SelfSizing 뷰 구현 개념은 이전 포스팅 글 참고
    • 이유) UITextView 레이아웃 계산을 내부적으로 잘못하여 스크롤이 위로 튕기는 것으로 예측하여 SelfSizing을 사용
class SelfSizingTextView: UITextView {
    override var intrinsicContentSize: CGSize {
        contentSize
    }
    
    override func layoutSubviews() {
        invalidateIntrinsicContentSize()
        super.layoutSubviews()
    }
}
  • 이 textView를 사용하여 isScollEnabled를 true로 설정
private let textView = {
	...
    textView.isScrollEnabled = true
    ...
    return t
}()

 

결과)

  • 입력할 때 자동으로 스크롤이 안되는 현상이 존재
    • -> scrollToCursor를 사용하여 입력되는 동시에 스크롤이 해당 지점으로 스크롤되게끔 수동으로 지정
    • scrollToCursor 개념은 이전 포스팅 글 참고
extension ViewController: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        scrollView.scrollToCursor(in: textView)
    }
    
}

extension UIScrollView {
    func scrollToCursor(in textView: UITextView) {
        guard let selectedRange = textView.selectedTextRange else { return }
        let cursorRect = textView.caretRect(for: selectedRange.start)
        let cursorRectInScrollView = textView.convert(cursorRect, to: self)
        let visibleRect = CGRect(x: 0, y: contentOffset.y, width: bounds.size.width, height: bounds.size.height)
        
        if !visibleRect.contains(cursorRectInScrollView.origin) {
            scrollRectToVisible(cursorRectInScrollView, animated: true)
        }
    }
}

완성) 입력하는 동시에 스크롤도 되는 상태

* 전체 코드: https://github.com/JK0369/ExTextInputBugIniOS16

Comments