Storyboardを使わずシンプルなタブブラウザを作る②
Storyboardを使わずシンプルなタブブラウザを作る①の続きです。
↓↓↓
タブ一覧画面を実装
タブ一覧画面のファイルTabVc.swiftを新規作成します。
NavigationControllerの階層はタブ画面>>ブラウザ画面になるので
AppDelegate.swiftを↓のように修正します。
let viewController: BrowserVC = BrowserVC()
↓
let viewController: TabVC = TabVC()
タブ画面はUICollectionViewで実装します。
//
// 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を利用します。
まだ現時点ではタブデータが作れないので空のオブジェクトでデータソースを作って実行してみます。
// ↓viewDidLoad()に一時的に追加
// TEST用データ
tabDataList = [TabData(),TabData(),TabData(),TabData(),TabData()]
↓↓↓
それでは実際に、前回作ったBrowswerVCと連携させます。
タブを作成し、テーブルソースにセットして遷移
// 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に戻る処理を実装します。
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)
}
また、セルタップ時のイベントでタブを選択した時に再度ブラウザ画面を開く処理を追加します。
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
}
ここで一度実行してみます。
↓
このままだとタブが真っ白なのでページ読み込み完了時にWebView上のサムネイルを保存する処理を追加します。
@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メソッドを実装します
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()を修正します。
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)
}
↓実行
問題ないようです!
タブの追加・削除機能を実装
続けて「+」ボタンタップ時のイベントでタブを追加機能、
CollectionViewの「☓」ボタンタップ時のイベントでタブの削除機能を実装していきます。
// MARK: - ボタンアクション
func onClickAddBarButton(sender : UIButton)
{
// タブを追加
self.createNewTab()
}
func onClickDelete(sender : UIButton){
// タブを閉じる
tabDataList.remove(at: sender.tag)
collectionView.reloadData()
}
↓↓実行
今回はここまでです。
※2017.04.18
Swift3.0で書き直しました。