Swift・iOS

Swiftを中心に学んだことを記録に残すブログです。技術に関係ない記事もたまに書いています。

【SwiftUI】NavigationLinkで遷移した画面の上部に謎の余白が発生する原因

 

はじめに

「原因がただの凡ミス」系の記事です笑 同様の現象で悩む方はいないかと思いますが、私はすぐに原因に気付けず時間を失ったので一応記事に残しておきます・・・。

 

開発環境

macOS Catalina 10.15.7

Xcode 12.2

・Swift 5.3.1 

 

本題

謎の余白問題

NavigationLinkで遷移した先の画面で、まず下にスクロールし、その後上にスクロールするとナビゲーションバーとコンテンツの間に余白が発生してしまう。遷移直後は上部に余白はないのになぜ? 

f:id:hfoasi8fje3:20201229175417g:plain

※遷移先(お知らせ詳細画面)の表示内容に関して

適当にURLを設定したので、お知らせ詳細画面なのにAppleのサイトが表示されていますがお許しください・・・。

 

問題発生時のコード

NewsListView.swift

お知らせ一覧画面(遷移前のView)

import SwiftUI

struct NewsListView: View {
    var body: some View {
        NavigationView {
            List(newsDataArray) { item in
                NavigationLink(destination: WebView(urlString: item.urlString)) {
                    NewsListRowView(newsData: item)
                }
            }
            .navigationTitle("お知らせ")
        }
    }
}

struct NewsListView_Previews: PreviewProvider {
    static var previews: some View {
        NewsListView()
    }
}

 

WebView.swift

お知らせ詳細画面(遷移先のView)

【SwiftUI】WKWebViewを使えるようにする - Swift・iOSの実装と同じです。

import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    var urlString: String
    
    class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate {
        var parent: WebView
        
        init(_ parent: WebView) {
            self.parent = parent
        }
        
        // "target="_blank""が設定されたリンクも開けるようにする
        func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
            if navigationAction.targetFrame == nil {
                webView.load(navigationAction.request)
            }
            return nil
        }
        
        // URLごとに処理を制御する
        func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
            if let url = navigationAction.request.url?.absoluteString {
                if (url.hasPrefix("https://apps.apple.com/")) {
                    guard let appStoreLink = URL(string: url) else {
                        return
                    }
                    UIApplication.shared.open(appStoreLink, options: [:], completionHandler: { (succes) in
                    })
                    decisionHandler(WKNavigationActionPolicy.cancel)
                } else if (url.hasPrefix("http")) {
                    decisionHandler(WKNavigationActionPolicy.allow)
                } else {
                    decisionHandler(WKNavigationActionPolicy.cancel)
                }
            }
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        
        return webView
    }
    
    func updateUIView(_ webView: WKWebView, context: Context) {
        // makeCoordinatorで生成したCoordinatorクラスのインスタンスを指定
        webView.uiDelegate = context.coordinator
        webView.navigationDelegate = context.coordinator
        
        // スワイプで画面遷移できるようにする
        webView.allowsBackForwardNavigationGestures = true
        
        let url = URL(string: urlString)!
        let request = URLRequest(url: url)
        webView.load(request)
    }
}

 

原因

遷移先のnavigationBarTitleDisplayModeの設定が漏れていたため・・・。

※参考

https://developer.apple.com/documentation/swiftui/link/navigationbartitledisplaymode(_:)

 

遷移前のnavigationBarTitleDisplayModeの設定はデフォルトのままなので、"navigationBarTitleDisplayMode(.large)"。本来は遷移先のnavigationBarTitleDisplayModeの設定も必要だが、ここの対応が漏れたことで意図せず一部遷移前の設定が反映されてしまった可能性がある。謎の余白の正体は、"navigationBarTitleDisplayMode(.large)"の表示領域であり、タイトル名も設定していなかったため余白のように見えていただけ。

 

解決方法

WebViewを表示するNewsDetailViewを追加して、WebViewにnavigationBarTitleDisplayModeを設定する。

NewsDetailView.swift

import SwiftUI

struct NewsDetailView: View {
    var urlString: String
    var title: String
    
    var body: some View {
        WebView(urlString: urlString)
            .navigationTitle(title)
            .navigationBarTitleDisplayMode(.inline)
    }
}

struct NewsDetailView_Previews: PreviewProvider {
    static var previews: some View {
        NewsDetailView(urlString: "https://www.apple.com/jp/", title: "")
    }
}

 

遷移先の画面をNewsDetailViewに変更。 

NewsListView.swift

import SwiftUI

struct NewsListView: View {
    var body: some View {
        NavigationView {
            List(newsDataArray) { item in
                NavigationLink(destination: NewsDetailView(urlString: item.urlString, title: item.title)) {
                    NewsListRowView(newsData: item)
                }
            }
            .navigationTitle("お知らせ")
        }
    }
}

struct NewsListView_Previews: PreviewProvider {
    static var previews: some View {
        NewsListView()
    }
}

 

おわりに

これからも凡ミスしながら少しずつSwiftUIを学んでいきたいと思います・・・笑

 

参考

https://developer.apple.com/documentation/swiftui/link/navigationbartitledisplaymode(_:)

詳細! SwiftUI iPhoneアプリ開発入門ノート iOS 13 + Xcode11対応

↓上記書籍のXcode 12対応版です。

詳細! SwiftUI iPhoneアプリ開発入門ノート[2020] iOS 14+Xcode 12対応