【Swift】Storyboardを使わずシンプルなタブブラウザを作る②

#Storyboardを使わずシンプルなタブブラウザを作る②




<a href="http://qiita.com/penguin1121/items/1ffb1a8781a60f9192dd">Storyboardを使わずシンプルなタブブラウザを作る①</a>の続きです。



↓↓↓

##タブ一覧画面を実装

タブ一覧画面のファイルTabVc.swiftを新規作成します。

![らくがき.png](https://qiita-image-store.s3.amazonaws.com/0/86259/c3288748-7306-fe23-07a6-7bc5227d7951.png)

NavigationControllerの階層はタブ画面>>ブラウザ画面になるので
AppDelegate.swiftを↓のように修正します。

``` AppDelegate.swift

let viewController: BrowserVC = BrowserVC()
    ↓
let viewController: TabVC = TabVC()
```

タブ画面はUICollectionViewで実装します。

``` TabVc.swift
//
//  TabVc.swift
//  TabBrowser
//
//  Created by USER on 2016/03/08.
//  Copyright © 2016年 User. All rights reserved.
//
import WebKit
import UIKit


// MARK: - タブを保持するコンテナクラス
class TabData
{
    var webView:WKWebView!
    var image:UIImage!
    
    deinit{
        webView = nil
        image = nil
    }
}

class TabVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
    
    var collectionView : UICollectionView!
    var tabDataList:[TabData] = []
    var myTabIndexPathRow : Int = 0
    
    override func viewDidLoad()
    {
        super.viewDidLoad();
        
        // CollectionViewを作成する
        let layout = UICollectionViewFlowLayout()
        layout.itemSize = CGSize(width:self.view.frame.width/2, height:(self.view.frame.height-64)/2)
        layout.minimumInteritemSpacing = 0.0;
        layout.minimumLineSpacing = 0.0;
        collectionView = UICollectionView(frame:CGRect(x:0, y:0, width:self.view.frame.width, height:self.view.frame.height), collectionViewLayout: layout)
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "UICollectionViewCell")
        
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.isPagingEnabled = true
        collectionView.clipsToBounds = true
        
        self.view.addSubview(collectionView)
        
        let addBarButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.add, target: self, action: #selector(onClickAddBarButton))
        self.navigationItem.setLeftBarButton(addBarButton, animated: true)
        
    }
    
    override func viewWillAppear(_ animated: Bool) {
        
        collectionView.reloadData()
    }
    
    // MARK: - TableViewDelegate
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        // セル選択時
        return
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        // DataSourceの件数を返す
        return self.tabDataList.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cell : UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "UICollectionViewCell",
                                                                             for: indexPath) as UICollectionViewCell
        
        // Cellの再利用
        for subview in cell.contentView.subviews {
            subview.removeFromSuperview()
        }
        
        // タイトルラベル
        let textView = UITextView(frame: CGRect(x:0,y:10,width:cell.frame.width,height:50))
        if(tabDataList[indexPath.row].webView != nil){
            textView.text = tabDataList[indexPath.row].webView.title
        }
        textView.font = UIFont.systemFont(ofSize: CGFloat(10))
        textView.backgroundColor = UIColor.clear
        textView.textColor = UIColor.white
        textView.textAlignment = NSTextAlignment.center
        textView.isEditable = false
        cell.contentView.addSubview(textView)
        
        // UIImageView
        let thumbNailImage = UIImageView(frame: CGRect(x:(cell.frame.width - cell.frame.width*0.75)/2, y:55, width:cell.frame.width*0.75, height:cell.frame.height*0.75))
        thumbNailImage.image = tabDataList[indexPath.row].image
        thumbNailImage.backgroundColor = UIColor.white
        cell.contentView.addSubview(thumbNailImage)
        
        // 削除ボタン
        let btnDeleteImage:UIImage!
        btnDeleteImage = UIImage(named: "closeTab")! as UIImage
        
        let btnDelete   = UIButton()
        btnDelete.frame = CGRect(x:0, y:0, width:25, height:25)
        btnDelete.layer.position = CGPoint(x: (cell.frame.width - cell.frame.width*0.75)/2, y:55)
        btnDelete.setImage(btnDeleteImage, for: .normal)
        btnDelete.addTarget(self, action: #selector(onClickDelete), for:.touchUpInside)
        btnDelete.tag = indexPath.row
        cell.contentView.addSubview(btnDelete);
        
        return cell
    }
    // MARK: - ボタンアクション
    func onClickAddBarButton(sender : UIButton)
    {
        // タブ追加ボタン
    }
    func onClickDelete(sender : UIButton){
        // タブを閉じる
    }
}

```

タブデータの保持にはコンテナクラスのTabDataを利用します。
まだ現時点ではタブデータが作れないので空のオブジェクトでデータソースを作って実行してみます。

``` TabVc.swift
// ↓viewDidLoad()に一時的に追加
// TEST用データ
tabDataList = [TabData(),TabData(),TabData(),TabData(),TabData()]
```

↓↓↓

![スクリーンショット 2016-03-09 0.19.41.png](https://qiita-image-store.s3.amazonaws.com/0/86259/c6e0810b-bb7e-c01b-8224-95417a908305.png)

それでは実際に、前回作ったBrowswerVCと連携させます。


タブを作成し、テーブルソースにセットして遷移

``` TabVc.swift
// MARK: - タブを生成
func createNewTab(url:String! = nil){
    self.myTabIndexPathRow = self.tabDataList.count
    self.tabDataList.append(TabData())              
    self.navigationController?.pushViewController(BrowserVC(theDelegate:self ,wKWebView: nil,url:url), animated: false)
}
```

BrowserViewのinitメソッドでTabVcのdelegateを渡します。
ツールバーのタブボタンをタップした時にTabVcに戻る処理を実装します。

``` Browser.swift
weak var delegate: AnyObject?

init(delegate: AnyObject?, wKWebView:WKWebView!,url:String!) {
    super.init(nibName: nil, bundle: nil)
    self.delegate = delegate
    self.webView = wKWebView
    // ナビゲーションの戻るを非表示
    self.navigationItem.setHidesBackButton(true, animated:false)
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

required override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: Bundle!) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
/*
 ~略~
*/

func onClickTabBarButton(sender: UIButton) {
    // TabVcに戻る
    navigationController?.popToViewController(navigationController!.viewControllers[0], animated: false)
}
```

また、セルタップ時のイベントでタブを選択した時に再度ブラウザ画面を開く処理を追加します。

```TabVc.swift
private func collectionView(collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    // 選択したタブを保持
    self.myTabIndexPathRow = indexPath.row
    // ブラウザ画面に遷移
    self.navigationController?.pushViewController(BrowserVC(delegate:self ,wKWebView: self.tabDataList[indexPath.row].webView,url:nil), animated: false)
    return
}
```

ここで一度実行してみます。

↓

![fc033ed12afd2cab10c83867f86e07bf.gif](https://qiita-image-store.s3.amazonaws.com/0/86259/6c344869-4783-6885-2c3b-72a10433953b.gif)

このままだとタブが真っ白なのでページ読み込み完了時にWebView上のサムネイルを保存する処理を追加します。

```BrowserVc.swift
@objc protocol BrowserVcDelegate {
    // デリゲートメソッド定義
    func saveTab(wkWebView:WKWebView)
}

// MARK: - プログレスバーの更新(KVO)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    if(keyPath == "estimatedProgress"){
        let progress : Float = Float(webView.estimatedProgress)
        if(progressView != nil){
            // プログレスバーの更新
            if(progress < 1.0){
                progressView.setProgress(progress, animated: true)
                UIApplication.shared.isNetworkActivityIndicatorVisible = true
                self.navigationItem.rightBarButtonItem = stopBtn
            }else{
                // 読み込み完了
                progressView.setProgress(0.0, animated: false)
                UIApplication.shared.isNetworkActivityIndicatorVisible = false
                self.navigationItem.rightBarButtonItem = reloadBtn
                searchBar.text = webView.url?.absoluteString
                
                // タブのサムネイル画像を保存
                self.delegate?.saveTab(wkWebView:self.webView)
            }
        }
    }
}
```

TabVcにsaveTabメソッドを実装します

```TabVc.swift
func saveTab(wkWebView:WKWebView){
    
    // タブの保存
    self.tabDataList[self.myTabIndexPathRow].webView = wkWebView
    // すぐ実行すると真っ白な画像が撮れる為 少し間を空けてサムネイル画像を保存
    NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: Selector("saveTabImageExec"), userInfo: nil, repeats: false)
    
}


// MARK: - タブのサムネイル保持
func saveTabImageExec(){
    
    let queue = dispatch_queue_create("saveImage", DISPATCH_QUEUE_SERIAL)
    dispatch_async(queue, {
        let webView = self.tabDataList[self.myTabIndexPathRow].webView
        UIGraphicsBeginImageContextWithOptions(webView.bounds.size, true, 0);
        webView.drawViewHierarchyInRect(webView.bounds, afterScreenUpdates: false);
        let snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        self.tabDataList[self.myTabIndexPathRow].image = snapshotImage
    })
}
```
drawViewHierarchyInRectメソッドを使って
WebViewに表示しているページを画像として保存しています。
ただしすぐに実行すると真っ白なページが撮れるため0.3秒後に撮るようにしています。


続けてBrowserVcのviewDidLoad()を修正します。

```BrowserVc.swift
override func viewDidLoad() {
    super.viewDidLoad()
    
    // ステータスバーの高さを取得
    let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height
    
    // ナビゲーションバーの高さを取得
    let navBarHeight = self.navigationController?.navigationBar.frame.size.height

    // ツールバー
    let toolbar = UIToolbar(frame: CGRect(x:0, y:self.view.bounds.size.height - 44, width:self.view.bounds.size.width, height:40.0))
    toolbar.layer.position = CGPoint(x: self.view.bounds.width/2, y: self.view.bounds.height-20.0)
    toolbar.barStyle = .default
    toolbar.tintColor = UIColor.white
    
    // 戻るボタン
    let backBtnView = UIButton(frame: CGRect(x:0, y:0, width:24, height:24))
    backBtnView.setBackgroundImage(UIImage(named: "back"), for: .normal)
    backBtnView.addTarget(self, action: #selector(onClickBackBarButton), for: .touchUpInside)
    let backBtn = UIBarButtonItem(customView: backBtnView)
    
    // 進むボタン
    let forwardBtnView = UIButton(frame: CGRect(x:0, y:0, width:24, height:24))
    forwardBtnView.setBackgroundImage(UIImage(named: "forward"), for: .normal)
    forwardBtnView.addTarget(self, action: #selector(onClickForwardBarButton), for: .touchUpInside)
    let forwardBtn = UIBarButtonItem(customView: forwardBtnView)
    
    // ブックマークボタン
    let bookmarkBtnView = UIButton(frame: CGRect(x:0, y:0, width:24, height:24))
    bookmarkBtnView.setBackgroundImage(UIImage(named: "bookmark"), for: .normal)
    bookmarkBtnView.addTarget(self, action: #selector(onClickBookmarkBarButton), for: .touchUpInside)
    let bookmarkBtn = UIBarButtonItem(customView: bookmarkBtnView)
    
    // ブックマークボタン長押しのジェスチャー
    let bookmarkLongPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressBookmark))
    bookmarkLongPressGesture.minimumPressDuration = 1.0// 長押し-最低1秒間は長押しする.
    bookmarkLongPressGesture.allowableMovement = 150// 長押し-指のズレは15pxまで.
    bookmarkBtnView.addGestureRecognizer(bookmarkLongPressGesture)
    
    // タブボタン
    let tabBtnView = UIButton(frame: CGRect(x:0, y:0, width:24, height:24))
    tabBtnView.setBackgroundImage(UIImage(named: "tab"), for: .normal)
    tabBtnView.addTarget(self, action: #selector(onClickTabBarButton), for: .touchUpInside)
    let tabBtn = UIBarButtonItem(customView: tabBtnView)
    
    // スペーサー
    let flexibleItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
    
    // ツールバーに追加する.
    toolbar.items = [backBtn, flexibleItem, forwardBtn, flexibleItem, bookmarkBtn, flexibleItem, tabBtn]
    self.view.addSubview(toolbar)
    
    // 検索バーを作成する.
    searchBar = UISearchBar(frame:CGRect(x:0, y:0, width:270, height:80))
    searchBar.delegate = self
    searchBar.layer.position = CGPoint(x: self.view.bounds.width/2, y: 20)
    searchBar.searchBarStyle = UISearchBarStyle.minimal
    searchBar.placeholder = "URLまたは検索ワード"
    searchBar.tintColor = UIColor.cyan
    // 余計なボタンは非表示にする.
    searchBar.showsSearchResultsButton = false
    searchBar.showsCancelButton = false
    searchBar.showsBookmarkButton = false
    
    // UINavigationBar上に、UISearchBarを追加
    self.navigationItem.titleView = searchBar
    
    // Reloadボタン
    let reloadBtnView = UIButton(frame: CGRect(x:0, y:0, width:24, height:24))
    reloadBtnView.setBackgroundImage(UIImage(named: "reload"), for: .normal)
    reloadBtnView.addTarget(self, action: #selector(onClickReload), for: .touchUpInside)
    reloadBtn = UIBarButtonItem(customView: reloadBtnView)
    self.navigationItem.rightBarButtonItem = reloadBtn
    
    // Stopボタン
    let stopdBtnView = UIButton(frame: CGRect(x:0, y:0, width:24, height:24))
    stopdBtnView.setBackgroundImage(UIImage(named: "stop"), for: .normal)
    stopdBtnView.addTarget(self, action: #selector(onClickStop), for: .touchUpInside)
    stopBtn = UIBarButtonItem(customView: stopdBtnView)
    
    // ProgressViewを作成する.
    progressView = UIProgressView(frame: CGRect(x:0, y:0, width:self.view.bounds.size.width * 2, height:20))
    progressView.progressTintColor = UIColor.green
    progressView.trackTintColor = UIColor.white
    progressView.layer.position = CGPoint(x:0, y:(self.navigationController?.navigationBar.frame.size.height)!)
    progressView.transform = CGAffineTransform(scaleX: 1.0, y: 2.0)
    self.navigationItem.titleView?.addSubview(progressView)
    
    if(self.webView == nil){
        // 新規WKWebViewを生成
        webView = WKWebView(frame:CGRect(x:0, y:statusBarHeight+navBarHeight!, width:self.view.bounds.size.width, height:self.view.bounds.size.height-(statusBarHeight+navBarHeight!+40)))
        
        // Googleを表示
        let urlString = "http://www.google.com"
        let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
        let url = NSURL(string: encodedUrlString!)
        let request = NSURLRequest(url: url! as URL)
        webView.load(request as URLRequest)
    }
    else {
        // 既存のWKWebViewを開いた時 検索バーにURLをセット
        self.searchBar.text = webView.url?.absoluteString
    }
    
    // フリップで進む・戻るを許可
    webView.allowsBackForwardNavigationGestures = true
    
    self.webView.navigationDelegate = self
    self.webView!.uiDelegate = self
    
    // Viewに貼り付け
    self.view.addSubview(webView)
    
    // WebViewの読み込み状態を監視する
    self.webView.addObserver(self, forKeyPath:"estimatedProgress", options:.new, context:nil)
    
}
```

↓実行　

![ba146792ff814d3100b09357340c0b3c.gif](https://qiita-image-store.s3.amazonaws.com/0/86259/03a4326e-6266-be4a-6050-f566f6dde433.gif)


問題ないようです！

##タブの追加・削除機能を実装

続けて「＋」ボタンタップ時のイベントでタブを追加機能、
CollectionViewの「☓」ボタンタップ時のイベントでタブの削除機能を実装していきます。

```TabVc.swift
// MARK: - ボタンアクション
func onClickAddBarButton(sender : UIButton)
{
    // タブを追加
    self.createNewTab()
}
func onClickDelete(sender : UIButton){
    // タブを閉じる
    tabDataList.remove(at: sender.tag)
    collectionView.reloadData()
}
```

↓↓実行

![a8d28c18ed3dcb3ad46bcc382df4461c.gif](https://qiita-image-store.s3.amazonaws.com/0/86259/9861c746-fe2f-7c95-3b8a-5b8afbdf3754.gif)


##今回はここまでです。

※2017.04.18
 Swift3.0で書き直しました。