Edited at

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

More than 1 year has passed since last update.


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

Storyboardを使わずシンプルなタブブラウザを作る①の続きです。

↓↓↓


タブ一覧画面を実装

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

らくがき.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

それでは実際に、前回作った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

このままだとタブが真っ白なのでページ読み込み完了時に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

問題ないようです!


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

続けて「+」ボタンタップ時のイベントでタブを追加機能、

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


TabVc.swift

// MARK: - ボタンアクション

func onClickAddBarButton(sender : UIButton)
{
// タブを追加
self.createNewTab()
}
func onClickDelete(sender : UIButton){
// タブを閉じる
tabDataList.remove(at: sender.tag)
collectionView.reloadData()
}

↓↓実行

a8d28c18ed3dcb3ad46bcc382df4461c.gif


今回はここまでです。

※2017.04.18

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