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))
}
}
* 참고