キュレーションアプリを作る(その2)
横スライドのタブメニューライブラリPageMenuを使ったキュレーションアプリの実装手順を2回に分けて紹介します。本記事はその2回めです。今回はブラウズ機能を追加します。
1.ビューコントローラーとセグエの追加
起動時の各Webビュー内でブラウズできるようにしてもよいのですが、今回はブラウズ用のビューコントローラーを追加することにしましたので、そこに遷移するためのセグエも追加します。
2.セグエの設定
セグエのidentifierを設定します。今回はbrowseにしました。
3.ブラウズ用クラスの追加
ブラウズ用のクラスを作成します。メニュー:File → New → Fileと辿り、「Cocoa Touch Class」をダブルクリック、UIViewController
をサブクラスに選択します。今回はBrowseViewControllerという名前にしました。
4.ストーリーボード上のViewControllerと紐付け
ストーリーボード上のブラウズ用のビューコントローラーにBrowseViewController
を割り当てます。
5.ブラウズクラスの実装
実装に伴い、BackArrowという戻るボタンのイメージセットを用意します。
イメージセットの作成にはココを参考にしてください。
BrowseViewController.swif
// Swift3.0
import UIKit
class BrowseViewController: UIViewController,UIWebViewDelegate {
// 別ビューから受け取るリクエスト
var request:URLRequest!
// ウェブビュー
var webView:UIWebView!
// 「戻る」ボタンを配置するツールバー
var toolbar:UIToolbar?
override func viewDidLoad() {
super.viewDidLoad()
// ウェブビューを画面いっぱいに作成
webView = UIWebView(frame: self.view.bounds)
webView.delegate = self
// URLリクエスト
let req = URLRequest(url:request.url!)
// ウェブビューにロード
webView.loadRequest(req)
// ウェブビューを表示
self.view.addSubview(webView)
// 「ホームへ戻る」ボタンの作成
let button = UIButton(frame: CGRect(x:0,y:(self.view.bounds.height-40),width:self.view.bounds.width,height:40))
button.titleLabel?.font = UIFont.systemFont(ofSize: 14)
button.setTitle("ホームへ戻る", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.backgroundColor = UIColor.lightGray
button.addTarget(self, action: #selector(homeBack), for: .touchUpInside)
self.view.addSubview(button)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == UIWebViewNavigationType.linkClicked {
// ToolBarの制御
if toolbar == nil {
toolbar = UIToolbar(frame: CGRect(x:0,y:0,width:self.view.bounds.width,height:70))
// Bar Button Item
let img = UIImage(named:"BackArrow")!.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
let item = UIBarButtonItem(image: img,
style: UIBarButtonItemStyle.plain,
target: nil,
action: #selector(BrowseViewController.backBrowse))
let items = [item]
toolbar?.items = items
self.view.addSubview(toolbar!)
}
}
return true
}
func webViewDidFinishLoad(_ webView: UIWebView) {
if webView.isLoading {
return
}
toolbar?.isHidden = true
// 戻るボタン
if self.webView.canGoBack {
toolbar?.isHidden = false
} else {
toolbar?.isHidden = true
}
}
func backBrowse(){
self.webView.goBack()
}
func homeBack() {
self.dismiss(animated: true, completion: nil)
}
}
6.ContentsViewControllerとViewControllerの改修
その1で作成済みのContentsViewController.swiftに以下を追加します。masterViewPointerはContentsViewControllerからViewController上のセグエを実行可能にするためのものです(protocol不要のデリゲートパタンのようなもの)。func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool
はリクエストをフックするUIWebviewのデリゲートメソッドです。リクエストをフックしてリンククリックであれば、ViewContoroller上のbrowseメソッドを実行し、そのリクエストをキャンセル(return false)します。
var masterViewPointer:ViewController?
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == UIWebViewNavigationType.linkClicked {
masterViewPointer?.browse(request)
return false
}
追加後のContentsViewController.swift
import UIKit
class ContentsViewController: UIViewController ,UIWebViewDelegate{
var webView:UIWebView!
var siteUrl:String!
var masterViewPointer:ViewController?
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: Webview delegate
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == UIWebViewNavigationType.linkClicked {
masterViewPointer?.browse(request)
return false
}
return true
}
}
さらに、その1で作成したViewController.swiftに以下を追加します。ContentsViewControllerで発生したリクエストをViewControllerで受け取るための追加です。
var request:URLRequest?
controller.masterViewPointer = self
func browse(_ request:URLRequest) {
self.request = request
performSegue(withIdentifier: "browse", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "browse" {
let vc = segue.destination as! BrowseViewController
vc.request = self.request
}
}
追加後のViewController.swift
import UIKit
class ViewController: UIViewController {
// インスタンス配列
var controllerArray : [UIViewController] = []
var pageMenu : CAPSPageMenu?
var request:URLRequest?
// サイト情報
let siteInfo:[Dictionary<String,String>] = [
["title":"ヤフー!知恵袋","url":"http://chiebukuro.yahoo.co.jp/"],
["title":"教えて!goo","url":"http://oshiete.goo.ne.jp/"],
["title":"OKWAVE","url":"http://okwave.jp/"],
["title":"発言小町","url":"http://komachi.yomiuri.co.jp/"],
["title":"BIGLOBEなんでも相談室","url":"http://soudan.biglobe.ne.jp/sp/"]
]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
for site in siteInfo {
let controller:ContentsViewController = ContentsViewController(nibName: "ContentsViewController", bundle: nil)
controller.masterViewPointer = self
controller.title = site["title"]!
controller.siteUrl = site["url"]!
controller.webView = UIWebView(frame : self.view.bounds)
controller.webView.delegate = controller
controller.view.addSubview(controller.webView)
let req = URLRequest(url: URL(string:controller.siteUrl!)!)
controller.webView.loadRequest(req)
controllerArray.append(controller)
}
// Customize menu (Optional)
let parameters: [CAPSPageMenuOption] = [
.scrollMenuBackgroundColor(UIColor.white),
.viewBackgroundColor(UIColor.white),
.bottomMenuHairlineColor(UIColor.blue),
.selectionIndicatorColor(UIColor.red),
.menuItemFont(UIFont(name: "HelveticaNeue", size: 14.0)!),
.centerMenuItems(true),
.menuItemWidthBasedOnTitleTextWidth(true),
.menuMargin(16),
.selectedMenuItemLabelColor(UIColor.black),
.unselectedMenuItemLabelColor(UIColor.gray)
]
// Initialize scroll menu
let rect = CGRect(origin: CGPoint(x: 0,y :20), size: CGSize(width: self.view.frame.width, height: self.view.frame.height))
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: rect, pageMenuOptions: parameters)
self.addChildViewController(pageMenu!)
self.view.addSubview(pageMenu!.view)
pageMenu!.didMove(toParentViewController: self)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func browse(_ request:URLRequest) {
self.request = request
performSegue(withIdentifier: "browse", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "browse" {
let vc = segue.destination as! BrowseViewController
vc.request = self.request
}
}
}
7.ビルドしてみる
起動後にロードされるウェブビュー内のリンクをクリックするとBrowseViewへ遷移し、そのBrowseViewが持つwebView内でリクエストが展開されます。
8.補足
今回、戻るボタン付きツールバーの表示・非表示は、UIWebViewNavigationType.linkClicked
のみをフックして判定をおこなっています。フォームのポスト時などには対応していません。
参考:UIWebViewNavigationType
public enum UIWebViewNavigationType : Int {
case linkClicked
case formSubmitted
case backForward
case reload
case formResubmitted
case other
}
プロジェクト一式をGitHubに置いときます