관리 메뉴

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

[iOS - SwiftUI] 키보드와 스크롤 뷰 (키보드 올라올 때 TextField와의 최소 거리만큼 스크롤 방법, safeAreaPadding) 본문

iOS 응용 (SwiftUI)

[iOS - SwiftUI] 키보드와 스크롤 뷰 (키보드 올라올 때 TextField와의 최소 거리만큼 스크롤 방법, safeAreaPadding)

jake-kim 2025. 1. 23. 01:35

키보드와 스크롤 뷰

  • 스크롤 뷰 안에 있는 TextField를 탭하여 키보드가 등장할 때, 키보드와 TextField 사이의 거리 최소 32만큼 스크롤링 되는 방법?

ex) 키보드와 포커싱된 TextField 사이의 거리를 최소 32로 설정한 예제

구현 아이디어

  • 키보드 높이가 변하는 이벤트를 감지
  • 키보드가 등장하면 safeAreaPadding을 32로 설정, 키보드가 다시 들어가면 0으로 설정

구현

  • 키보드 이벤트 감지
    • 키보드 이벤트를 감지하는 ObservableObject 성격의 클래스 구현
public class KeyboardInfo: ObservableObject {
    public static var shared = KeyboardInfo()
    
    @Published public var height: CGFloat = 0
    
    private init() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIApplication.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIResponder.keyboardWillHideNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    }
    
    @objc func keyboardChanged(notification: Notification) {
        if notification.name == UIApplication.keyboardWillHideNotification {
            self.height = 0
        } else {
            self.height = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0
        }
    }
}
  • 공통화하여 사용하기 쉽게 ViewModifier로 구현
    • 키보드가 등장하면 safeAreaPadding을 주입받은 키보드와 텍스트필드 사이의 최소 거리로 설정, 키보드가 다시 들어가면 0으로 설정
struct KeyboardAware: ViewModifier {
    var minDisntance: CGFloat
    @ObservedObject private var keyboard = KeyboardInfo.shared
    
    func body(content: Content) -> some View {
        content
            .safeAreaPadding(.bottom, keyboard.height > 0 ? minDisntance : 0)
    }
}
  • ScrollView에다 해당 modifier를 붙여서 사용
struct ContentView: View {
    @State var text = "example"
    
    var body: some View {
        ScrollView {
            VStack(alignment: .leading) {
                ...
            }
        }
        .scrollToMinDistance(minDisntance: 32) // <-
    }
}

전체 코드)

struct ContentView: View {
    @State var text = "example"
    
    var body: some View {
        ScrollView {
            VStack(alignment: .leading) {
                ForEach(0 ..< 20) { i in
                    Text("Text \(i):")
                    TextField("Text", text: self.$text)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .padding(.bottom, 10)
                }
            }
            .padding()
        }
        .ignoresSafeArea(.keyboard, edges: .bottom)
        .simultaneousGesture(
            DragGesture()
                .onChanged { _ in
                    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                    
                })
        .scrollToMinDistance(minDisntance: 32)
    }
}

public class KeyboardInfo: ObservableObject {
    public static var shared = KeyboardInfo()
    
    @Published public var height: CGFloat = 0
    
    private init() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIApplication.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIResponder.keyboardWillHideNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    }
    
    @objc func keyboardChanged(notification: Notification) {
        if notification.name == UIApplication.keyboardWillHideNotification {
            self.height = 0
        } else {
            self.height = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0
        }
    }
}

struct KeyboardAware: ViewModifier {
    var minDisntance: CGFloat
    @ObservedObject private var keyboard = KeyboardInfo.shared
    
    func body(content: Content) -> some View {
        content
            .safeAreaPadding(.bottom, keyboard.height > 0 ? minDisntance : 0)
    }
}

extension View {
    public func scrollToMinDistance(minDisntance: CGFloat) -> some View {
        ModifiedContent(content: self, modifier: KeyboardAware(minDisntance: minDisntance))
    }
}

* 참고

- https://stackoverflow.com/questions/56491881/move-textfield-up-when-the-keyboard-has-appeared-in-swiftui/57235093#57235093

Comments