【Swift】 タブブラウザライクに、任意のボタンを押した際に対応するViewを最前面に表示する

会社で開発しようとしている、自社Webサービス専用のタブブラウザアプリの一番キモの部分の実現性評価ができたので、メモを残しておきます。

####開発環境
端末:MacBook Pro/MacOS 10.14.5(Mojave)
Xcode:10.2.1
Swift:5

####やったこと(ポイント)
①target="_blank"(新しいタブで開く)のリンクでも新しいViewを作成して表示される
②「タブ」ボタンを押した際、そのボタンに紐づく画面(WKWebView)が最前面に表示される

####実装
#####画面イメージ
![tabbrowse.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126315/a4db2a8a-3e4e-9fc6-d065-171c7f539e12.gif)

※gif作るのヘタですみません。。

①画面下部にある「AddView」ボタンを押したらタブが増えるよ
→②タブボタンの並びが画面幅より広くなるとスクロールできるようになるよ
→③`target="_blank"`のリンク押すとタブが追加されて遷移先画面が表示されるよ
→④タブ移動すると、ちゃんとそのタブで表示されていた内容で最前面表示されるよ
→⑤`target="_self"`のリンク押すと自タブ内で画面遷移するよ

という流れになっています。
④が一番言いたいトコのハズなのに、すごくわかりづらくなってしまいました。
(youtuberにはなれないな・・・)

#####ソースコード

```swift:ViewController.swift

import UIKit
import WebKit

class ViewController: UIViewController,
    WKNavigationDelegate,
    WKUIDelegate,
    UIScrollViewDelegate {
    
    @IBOutlet weak var tabScrollView: UIScrollView!
    @IBOutlet weak var btnAddWebView: UIButton!
    
    
    var tagNumber: Int! = 0
    
    var viewWidth: CGFloat!
    var viewHeight: CGFloat!
    
    var testUrl: URL!
    
    let userContentController = WKUserContentController()
    
    var originX:CGFloat = 0
    var progressView = UIProgressView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        viewWidth = UIScreen.main.bounds.width
        viewHeight = UIScreen.main.bounds.height
        
        btnAddWebView.frame = CGRect.init(x: 0,
                                          y: viewHeight - 70,
                                          width: viewWidth,
                                          height: 30)
        
        // 一応画面上部にタイトルを表示
        let lblTitle = UILabel()
        lblTitle.text = "タブ遷移実現性評価"
        lblTitle.textColor = .black
        lblTitle.font = UIFont.systemFont(ofSize: 24)
        lblTitle.textAlignment = .center
        lblTitle.frame = CGRect.init(x: 0, y: 0, width: viewWidth, height: 50)
        
        tabScrollView.frame = CGRect.init(x: 0,
                                          y: 44,
                                          width: viewWidth,
                                          height: 50)
        tabScrollView.contentSize = CGSize.init(width: 0,
                                                height: 50)
        
        view.addSubview(tabScrollView)
        view.addSubview(lblTitle)
    }
    
    @IBAction func addWebView(_ sender: Any) {
        // デモ用ページのサンプルHTML
        let urlString = "http://www.xxx.yy.zz"
        let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
        let url = URL.init(string: encodedUrlString!)
        
        addTab(url: url as! URL)
    }
    
    @objc func showView(sender: UIButton) {
        // 個人的にここが一番キモの部分。
        // 「押下されたタブボタンに対応する画面(WKWebView)を指定して呼び出す」
        // という部分のロジックをどう書けばいいのか分からず、だいぶ悩みました。

        // sender -> UIButtonのtag番号に対応するWKWebViewを、
        // view.viewWithTag(viewTag)で指定する、というやり方で上記処理を実現しています。
        let viewTag = sender.tag + 10000
        let targetView = view.viewWithTag(viewTag) as! WKWebView
        
        // 上記で指定した画面を、最前面に表示させる。
        view.bringSubviewToFront(targetView)
        
        // 全てのタブを未選択状態にした上で、選択されたタブの色を変える。
        for i in 1...tagNumber {
            let targetButton = view.viewWithTag(i) as! UIButton
            targetButton.backgroundColor = UIColor.init(red: 118/255, green: 197/255, blue: 57/255, alpha: 1)
            targetButton.setTitleColor(.white, for: .normal)
        }

        // 選択されていることを示す配色設定
        sender.backgroundColor = .white
        sender.setTitleColor(.init(red: 118/255, green: 197/255, blue: 57/255, alpha: 1), for: .normal)
    }
    
    // WebViewでのWeb画面の画面遷移に呼び出される。
    // target="_blank"やwindow.open()など、別タブで画面を表示しようとする際に、
    // 新しい画面を追加してそこに表示させる制御を行う
    func webView(_ webView: WKWebView,
                 createWebViewWith configuration: WKWebViewConfiguration,
                 for navigationAction: WKNavigationAction,
                 windowFeatures: WKWindowFeatures) -> WKWebView? {
        guard let url = navigationAction.request.url else {
            return nil
        }
        
        guard let targetFrame = navigationAction.targetFrame, targetFrame.isMainFrame else {
            
            addTab(url: url)
            
            return nil
        }
        
        return nil
    }
    
    // タブを追加する処理
    func addTab(url: URL) {
        let request = NSURLRequest(url: url as URL)
        
        // 画面とタブボタンの管理通番をカウントアップ
        tagNumber += 1

        // 新しい画面（WKWebView）の追加
        let webView = WKWebView()

        // 画面のtagはタブボタンのtag+10000の値に設定
        webView.tag = tagNumber + 10000

        webView.uiDelegate = self as WKUIDelegate
        webView.navigationDelegate = self as WKNavigationDelegate
        webView.load(request as URLRequest)
        webView.frame = CGRect.init(x: 0,
                                    y: 100,
                                    width: viewWidth,
                                    height: viewHeight - 170)
        
        // 新しい画面に紐づくタブボタンの追加
        let tabButton = UIButton()
        let tabButtonWidth:CGFloat = 200
        let tabButtonHeight:CGFloat = tabScrollView.frame.height

        // タブボタンの見た目設定
        tabButton.tag = tagNumber
        tabButton.titleLabel?.textAlignment = .center
        tabButton.setTitle("tabNumber" + String(tagNumber), for: .normal)
        tabButton.layer.cornerRadius = 3.0
        tabButton.layer.borderWidth = 1.5
        tabButton.layer.borderColor = UIColor.init(red: 87/255, green: 144/255, blue: 40/255, alpha: 1).cgColor
        
        // 全てのタブを未選択状態にした上で、選択されたタブの色を変える。
        for i in 1..<tagNumber {
            let targetButton = view.viewWithTag(i) as! UIButton
            targetButton.backgroundColor = UIColor.init(red: 118/255, green: 197/255, blue: 57/255, alpha: 1)
            targetButton.setTitleColor(.white, for: .normal)
        }
        
        tabButton.backgroundColor = .white
        tabButton.setTitleColor(UIColor.init(red: 118/255, green: 197/255, blue: 57/255, alpha: 1), for: .normal)
        tabButton.titleLabel?.font = UIFont.systemFont(ofSize: 15)
        
        // タブボタンを押下した際の処理はshowViewメソッドに記述。
        //押されたボタンの情報を引数senderとして渡す
        tabButton.addTarget(self,
                            action: #selector(showView(sender: )),
                            for: .touchUpInside)

        tabButton.frame = CGRect(x: originX,
                                 y: 0,
                                 width: tabButtonWidth,
                                 height: tabButtonHeight)
        
        // タブを追加するに伴い、スクロールバーの中の表示位置をずらす制御
        originX += tabButtonWidth
        
        tabScrollView.addSubview(tabButton)
        tabScrollView.contentSize = CGSize(width: originX,
                                           height: tabButtonHeight)
        
        view.addSubview(tabScrollView)
        view.addSubview(webView)
        
        // タブが増えた時、追加されたタブボタンが表示される位置までスクロール
        // タブが少なく、画面内にすべて表示できている時はスキップ
        if (originX >= viewWidth) {
            let offsetX: CGFloat = originX - viewWidth
            tabScrollView.setContentOffset(CGPoint.init(x: offsetX, y: 0), animated: true)
        }
        
    }
}
```

各ロジックの意味やポイントはコメントとして書いているので、そこから読んで頂ければ。


####感想など。
２年ほど前に挑戦した時は、「WebViewで別タブを起動する画面遷移ができない」という判断で一度諦めていたんですが、改めて挑戦したらけっこう完成イメージが見えてくるところまで実現性評価を進めることができました。（Swiftプログラマとして成長したなぁ、と。）

今回のアプリのターゲット端末がiPadなので、このような形で進めています。
iPhone用だとchromeでもSafariでも「今開いてる画面一覧」を表示するボタンがあって、「あの形で実装するの難しそうだなぁ」と思っていたので、タブレット用で助かりました。

まだまだ「どうやってタブ＋画面を閉じるか」であったり、課題はいろいろ残っていますが一番大きな問題は解決したと言える状態になったので、ここからの実装はあまり心をすり減らさずに取り組めると思います。

以上です。