ํ์ด๋ธ๋ฆฌ๋ ์ฑ์ ๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ์น๋ทฐ์์ ์คํฌ๋กค์ด ํ๊ฑฐ๋,
ํค๋ณด๋๊ฐ ์ฌ๋ผ์ฌ ๋ ๋ ์ด์์์ด ๊นจ์ง๊ฑฐ๋,
iOS Safari์ ๋ค๋ฅด๊ฒ ๋์ํ๋ ๋ฌธ์ ๋ฅผ ์์ฃผ ๋ง๋ฅ๋จ๋ฆฌ๊ฒ ๋๋ค.
ํนํ iOS์ WKWebView๋ ์๋๋ก์ด๋ WebView, ๋ธ๋ผ์ฐ์ Safari์ ๋์ ๋ฐฉ์์ด ๋ชจ๋ ๋ฌ๋ผ์
๊ธฐ๋ฅ ํ๋๋ฅผ ๊ตฌํํ๋ ๋ฐ๋ ๋ง์ ์ฃผ์๊ฐ ํ์ํ๋ค.
์ด๋ฒ ๊ธ์์๋ WKWebView์ ์คํฌ๋กค ๋์๊ณผ ๊ด๋ จํด ๊ฐ์ฅ ๋ง์ด ๋ฐ์ํ๋ ๋ฌธ์ ๋ค์ ์ ๋ฆฌํ๋ ค๊ณ ํ๋ค.
WKWebView ์คํฌ๋กค์ ๊ธฐ๋ณธ ๊ตฌ์กฐ
webView.scrollView
- WKWebView ์์ฒด๋ ์คํฌ๋กค์ ํ์ง ์๋๋ค.
- ๋ด๋ถ์ UIScrollView๊ฐ ๋ชจ๋ ์คํฌ๋กคโ๋ฐ์ด์คโ์ปจํ
์ธ inset ์กฐ์ ์ ๋ด๋นํ๋ค.
- ๊ทธ๋์ ๋๋ถ๋ถ์ ์คํฌ๋กค ์กฐ์โ์ค์ ์ ์ด ๊ฐ์ฒด์ ๋ํด ์ด๋ฃจ์ด์ง๋ค.
webView.scrollView.bounces = false
webView.scrollView.showsVerticalScrollIndicator = false
Safe Area์ ์คํฌ๋กค ๊ด๊ณ: iOS๋ง์ ํน์ ์ฒ๋ฆฌ
iOS๋ ๋
ธ์น/ํ ์ธ๋์ผ์ดํฐ ๋๋ฌธ์ Safe Area๊ฐ ์กด์ฌํ๋ค.
๊ทธ๋ฐ๋ฐ, WKWebView๊ฐ ์ด Safe Area์ ์ํธ์์ฉํ๋ฉด์ ์๋์น ์์ ์ฌ๋ฐฑ์ด๋ ํ ํ์์ด ๋ฐ์ํ ์ ์๋ค.
๊ทธ๋์ ์๋ ์์ฑ์ ๋ฐ๋์ ์ดํดํ๊ณ ๋์ด๊ฐ์ผํ๋ค.
webView.scrollView.contentInsetAdjustmentBehavior = .never
- .automatic: iOS๊ฐ Safe Area๋ฅผ ๊ณ ๋ คํด์ ์๋ ์กฐ์ . ์์์น ๋ชปํ ์ฌ๋ฐฑ ๋ฐ์ ๊ฐ๋ฅ
- .scollableAxes: ์คํฌ๋กค๋๋ ๋ฐฉํฅ๋ง ์๋ ์กฐ์
- .never: Safe Area๋ฅผ ์ ํ ๊ณ ๋ คํ์ง ์์
- .always: ๋ฌด์กฐ๊ฑด Safe Area ๋ฐ์
์ค๋ฌด์ ์ผ๋ก WKWebView์์๋ ๊ฑฐ์ ๋๋ถ๋ถ .never์ด ์ ๋ต์ด๋ค.
์๋!
SPA ์น์ฑ์ด ์์ฒด์ ์ผ๋ก padding-top / padding-bottom์ ๊ด๋ฆฌํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๊ธฐ ๋๋ฌธ์
์ค๋ณต inset์ด ์๊ฒจ ์๋จ/ํ๋จ์ด ๋ฐ๋ฆฐ ๋ ์ด์์์ด ๋ํ๋ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋์ WKWebView์์๋ .never๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ก๊ณ ์์ํ๋ ๊ฒ์ด ์์ ํ๋ค.
Overscroll Bounce ๋นํ์ฑํ
iOS ์น๋ทฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก
- ์คํฌ๋กค ์๋จ์ ๋น๊ธฐ๋ฉด ์๋๋ก ๋์ด๋๋ค.
- ์คํฌ๋ก ํ๋จ์ ๋น๊ธฐ๋ฉด ์๋ก ๋์ด๋๋ bounce ํจ๊ณผ๋ฅผ ๊ฐ์ง๋ค.
์ด ๊ธฐ๋ฅ์ด ์๋์น ์์ ํ๋ฉด ํ๋ค๋ฆผ์ ์์ธ์ด ๋๊ธฐ๋ ํ๋ค.
webView.scrollView.bounces = false
webView.scrollView.alwaysBounceVertical = false
๊ทผ๋ฐ ๋ ๊ฐ์ธ์ ์ผ๋ก 'bounces = true' ๋์์ด ์ต์ํ ๋๋.. ๋ง์ iOS ์ฑ๋ค์ด ํ์ฉํด๋์ ๊ฒ ๊ฐ๋ค.
true, false๋ ์๋ ์ค์ ์ ํด์ฃผ์ด์ผ ํญ์ ์คํฌ๋กค์ด ๊ฐ๋ฅํด์ง๋ค.
์ฆ, ์ด ์ค์ ์ ํด์ฃผ์ด์ผ bounce ์ค์ ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๋ฐ์๋๋ค.
webView.scrollView.isScrollEnabled = true
ํค๋ณด๋๊ฐ ์ฌ๋ผ์ฌ ๋ ์คํฌ๋กค ๊นจ์ง ํ์
ํ์ด๋ธ๋ฆฌ๋ ์ฑ์์ ๊ฐ์ฅ ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ์ด๋ค.
์๋ฅผ ๋ค๋ฉด,
- ํค๋ณด๋๊ฐ ์ฌ๋ผ์ค๋ฉด ์น์ด ๊ฐ์๊ธฐ ์๋ก ํ์ด ์ค๋ฅธ๋ค.
- input ํด๋ฆญ ์ content inset์ด ๊ฐ์์ค๋ฝ๊ฒ ๋ฐ๋๋ค.
- ์คํฌ๋กค ์์น๊ฐ ์ด์ํ๊ฒ ์ด๋ํ๋ค.
์ ํ์๋ค์ ์์ธ์ ๋ฐ๋ก iOS ํค๋ณด๋๊ฐ ๋ฑ์ฅํ ๋ ScrollView์ SafeAreaInsets / contentInset์ ์๋์ผ๋ก ์กฐ์ ํ๋ค.
๋ฐ๋ก ์ฌ๊ธฐ์ WKWebView์์๋ ์ด ์๋์กฐ์ ๋๋ฌธ์ ์น ๋ด๋ถ ๋ ์ด์์๊ณผ ์ถฉ๋ํ๊ฒ ๋๋ค.
ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์
์ฒซ ๋ฒ์งธ๋ก๋ ์์ ์ด์ผ๊ธฐ ํ '์๋ inset ์กฐ์ ์ ๋นํ์ฑํ'ํ๋ ๊ฒ์ด๋ค.
webView.scrollView.contentInsetAdjustmentBehavior = .never
๋ ๋ฒ์งธ๋ก๋ ํค๋ณด๋ ์๋ฆผ์ ์ง์ ์ฒ๋ฆฌํ๋ ๊ฒ์ด๋ค.
ํค๋ณด๋๊ฐ ์ฌ๋ผ๊ฐ ๋ ํ๋จ inset์ ์ง์ ๋ง์ถฐ์ฃผ๋ ๋ฐฉ์์ด๋ค.
NotificationCenter.default.addObserver(
forName: UIResponder.keyboardWillChangeFrameNotification,
object: nil,
queue: .main
) { [weak self] notification in
guard
let info = notification.userInfo,
let frame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
else { return }
let height = UIScreen.main.bounds.height - frame.origin.y
self?.webView.scrollView.contentInset.bottom = max(height, 0)
self?.webView.scrollView.verticalScrollIndicatorInsets.bottom = max(height, 0)
}
์ด๊ฑด ์ค๋ฌด์์๋ ๋ฐ๋์ ๋ฃ์ด์ผํ๋ ์ฒ๋ฆฌ๋ผ๊ณ ํ๋ค.
์น์์ ์คํฌ๋กค์ด ์ ๋จนํ๋ ๋ฌธ์ (ํนํ ํน์ ๋๋ฉ์ธ)
iOS WKWebView๋ ๋ถ๋ชจ UIScrollView์ ์น ๋ด๋ถ ์คํฌ๋กค์ด ์ถฉ๋ํ ์ ์๋ค.
๋ํ์ ์ผ๋ก ์๋์ ๊ฐ์ ํ์๋ค์ด๋ค.
- ํน์ ์์ญ์ด ์คํฌ๋กค ๋์ง ์๋๋ค.
- ์คํฌ๋กค์ด '๋ฑ๋ฑํจ'์ด ๋๊ปด์ง๋ค.
- ์กฐ๊ธ ์์ง์ด๋ค๊ฐ ๊ฐ์๊ธฐ ๋ฉ์ถ๋ค.
- position: fixed ์์๊ฐ ๋จ๋ฆฐ๋ค.
์ด๋ฌํ ํ์๋ค์ ์์ธ์ ์๋์ ๊ฐ๋ค.
- CSS์์ touch-action ๋ฏธ์ง์
iOS Safari/WebKit์ ์ฌ์ ํ touch-acion: pan-y ๋ฑ์ ์ ๋๋ก ์ง์ํ์ง ์๋๋ค.
๋ฐ๋ผ์ CSS ์คํฌ๋กค์ด ๋ถ๋๋ฝ์ง ์์์๋ ์๋ค.
๋ ์๋์ ๊ฐ์ ์์ธ๋ ์๋ค.
- overflow: scroll ์์ญ ์ด์
overflow: scroll ๋๋ overflow: auto๋ฅผ ๊ฐ์ง div ๋ด๋ถ๋ ๋ถ๋ชจ UIScrollView์ ์ถฉ๋ํ ์ ์๋ค.
iOS์์ momentum scrolling ํ์ฑํ๋ฅผ ํด์ฃผ์ด์ผํ๋ค.
.scroll-area {
-webkit-overflow-scrolling: touch;
}
์ด ์์ฑ์ด ์์ผ๋ฉด ๋ชจ๋ฐ์ผ Safari/WKWebView ๋ชจ๋ ์ ์์ ์ธ ์คํฌ๋กค ๋ชจ๋ฉํ ์ ์ ๊ณตํ์ง ์๋๋ค.
์น๊ณผ ๋ค์ดํฐ๋ธ ์คํฌ๋กค์ด ๋์์ ์๋ ๊ฒฝ์ฐ(์ด์ค์คํฌ๋กค)
๋ชจ๋ฐ์ผ ์น ํ๋ก์ ํธ์์ ๊ฐ์ฅ ํํ ๋ฌธ์ ์ค ํ๋์ด๋ค.
๋ํ์ ์ผ๋ก ์๋์ ๊ฐ์ ํ์๋ค์ ์๋ฏธํ๋ค.
- ์น ๋ด๋ถ ์คํฌ๋กค์ด ์์ง์ด๋๋ฐ ๋ค์ดํฐ๋ธ WKWebView๊น์ง ๊ฐ์ด ์์ง์ธ๋ค.
- ์คํฌ๋กค์ด ๋๋ฌ๋๋ฐ bounce๊ฐ ๋ ๋ฒ ๋ฐ์ํ๋ค.
- ์๋จ ๊ณ ์ ํค๋๊ฐ ๋จ๋ฆฐ๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ์๋์ ๊ฐ์ ์ฒ๋ฆฌ๊ฐ ํ์ํ๋ค.
- WKWebView์ bounce ์ ๊ฑฐ
- ์น ๋ด๋ถ ์คํฌ๋กค ์์ญ์ -webkit-overflow-scrolling: touch
- ๊ฐ๋ฅํ๋ฉด ์ฑ์์๋ overscroll-behavior ์ปจ๋๋กค์ ์น์์ ๊ฐ์ด ์ ์ฉ
html, body {
overscroll-behavior: none;
}
Safe Area์ ์คํฌ๋กค์ ๊ฒฐํฉ ๋ฌธ์
์ด๊ฒ ๋ฌด์จ ๋ฌธ์ ๋.
์๋ฅผ ๋ค์ด์ ์น์์ ์๋์ ๊ฐ์ด Safe Area padding์ ์ ์ฉํ๋ค๋ฉด,
padding-top: env(safe-area-inset-top);
๊ทธ๋ฆฌ๊ณ WKWebView๊ฐ ์๋์ ๊ฐ์ด SafeAreaLayoutGuide์ ๋ง์ถฐ์ ธ์๋ค๋ฉด,
webView.snp.makeConstraints {
$0.top.equalTo(view.safeAreaLayoutGuide)
}
(์ค๋
ํท ์ฌ์ฉ ์ฝ๋์)
์ด๋ ๊ฒ ๋๋ค๋ฉด, ์๋จ padding์ด 2๋ฒ ์ ์ฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ํค๋๊ฐ ๋ด๋ ค์์ ์ด์ํ ์ฌ๋ฐฑ์ด ์๊ธด๋ค.
์ฆ, ์ ๋ฆฌํ์๋ฉด ์น์ด SafeArea๋ฅผ ์ฒ๋ฆฌํ๋ค๋ฉด? ๋ค์ดํฐ๋ธ๋ SafeAreaLayoutGuide๋ฅผ ์ฐ์ง ์๋๋ค.
๋ง์ฝ ๋ค์ดํฐ๋ธ๊ฐ Safe Area๋ฅผ ์ฒ๋ฆฌํ๋ค๋ฉด? ์น์์ env()๋ฅผ ์ ๊ฑฐํ๋ค.
WKWebView๋ ๋จ์ํ ์น ์ปจํ
์ด๋์ฒ๋ผ ๋ณด์ด์ง๋ง, ์ค์ ๋ก๋ ์ฌ๋ฌ iOS ๊ณ ์ ๋์๊ณผ ๋ฐ์ ํ๊ฒ ์ฎ์ฌ ์๋ค.
ํนํ ์คํฌ๋กค๊ณผ Safe Area, ํค๋ณด๋, ๋ฐ์ฐ์ค์ฒ๋ผ ์ฌ์ฉ์ ๊ฒฝํ์ ์ง์ ์ ์ผ๋ก ์ํฅ์ ์ฃผ๋ ์์๋ค์
๋ค์ดํฐ๋ธ์ ์น ๋ ์ด์ด๊ฐ ์ด๋ฏ๊ฒ ๊ฐ์
ํ๋๋์ ๋ฐ๋ผ ์ ํ ๋ค๋ฅธ ๊ฒฐ๊ณผ๊ฐ ๋์จ๋ค.
๊ฒฐ๊ตญ ์ค์ํ ๊ฒ์ ์ด๋ค ๊ณ์ธต์ด ๋ฌด์์ ์ฑ
์์ง์ง ๋ช
ํํ ๊ฒฐ์ ํ๋ ๊ฒ์ด๋ค.
์ด๊ธฐ ๊ฐ๋ฐ ๋จ๊ณ์์ ์น ๊ฐ๋ฐ์์ ์ฑ ๊ฐ๋ฐ์ ์ฌ์ด์ ์ด ์์น์ด ํ์คํ ์ ๋ฆฌ๋์ด ์์ผ๋ฉด
๋๋ถ๋ถ์ ์คํฌ๋กคโ๋ ์ด์์ ๋ฌธ์ ๋ ์์ธก์ด ๊ฐ๋ฅํด์ง๊ณ ,
ํ์ด๋ธ๋ฆฌ๋ ์ฑ์์๋ iOS ๋ค์ดํฐ๋ธ์ ๊ฐ๊น๊ณ ์์ ์ ์ธ UX๋ฅผ ์ ๊ณตํ ์ ์๋ค.
๊ธฐ์กด์๋ ์ด๋ฏธ ๋ง๋ค์ด์ง ์น์ฑ์ ์ด์๋ฅผ ํด๊ฒฐํ๊ฑฐ๋ ์ ์ง๋ณด์ํ๋ ๋ฐ ์ง์คํ๋ค๋ฉด,
์ด๋ฒ์๋ ์น์ฑ ๊ธฐ๋ฐ ํ์ด๋ธ๋ฆฌ๋ ๊ตฌ์กฐ๋ฅผ ์ฒ์๋ถํฐ ์ง์ ๊ฐ๋ฐ์ ์ฐธ์ฌํ๋ฉด์
iOS์ ์ฌ๋ฌ ์์๊ฐ ์๊ฐ๋ณด๋ค ํจ์ฌ ์น๊ณผ ๊น๊ฒ ์ฐ๊ฒฐ๋์ด ์๋ค๋ ์ ์ ์๋กญ๊ฒ ๊นจ๋ซ๊ฒ ๋์๋ค.
์ด๋ฒ ์ ๋ฆฌ๊ฐ ๋ถ์กฑํ ๋ถ๋ถ๋ค๋ ๋ง๊ฒ ์ง๋ง,
์ด ์ ๋ฆฌ๊ฐ ์ฒ์ ์น์ฑ ๊ธฐ๋ฐ ํ์ด๋ธ๋ฆฌ๋ ์ฑ์ ๊ฒฝํํ๋ ๊ฐ๋ฐ์๋ค์๊ฒ๋
์กฐ๊ธ์ด๋๋ง ์ค์ง์ ์ธ ๋์์ด ๋๊ธฐ๋ฅผ ๋ฐ๋๋ค.
'๐' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [Swift] Swift Concurrency ์์ ์ ๋ณต (0) | 2025.11.11 |
|---|---|
| iOS ์น๋ทฐ ์๋จ ๋ ธ์น ๋์ํ๊ธฐ (0) | 2025.11.07 |
| [Swift] Actor, isolated, nonisolated (0) | 2025.10.28 |
| [Swift] iOS ์์ฒด์ธ์ฆ ๊ถํ์ '๊ฑฐ๋ถ' ์ํ๋ฅผ ๊ฐ์งํ ์ ์๋ค? (0) | 2025.10.26 |
| [Swift] Privacy Masking(Privacy Screen) ๊ตฌํํ๊ธฐ (0) | 2025.10.19 |