YoutubeのiOSアプリ(クローン)のコードを読む会 NavigationController編
はじめに
これまでYoutubeのクローンを教材に勉強してきて、
HomeVC
AccountVC
をやってきました。特に変則的なことはなかったのですが、Youtubeといったら使いやすいNavigationVCだと思います。
今回はそれを解説したいと思います。以下のようなものが対応しています。
NavigationViewControllerの使い方復習
まず、NavigationViewControllerの使い方を復習しようと思います。
参考にしたのは以下の記事です。
UINavigationController - UIKit | Apple Developer Documentation
1. どのようなものか
以下のような階層的なUIの構築には良いと思います。
2. 階層イメージ
以下の画像が参考になります。
3. 今回はどこに使われているか
たとえば今回の場合は、
のStoryBoardです。
そのような中で、まず
- MainVCを開く
そして、次のViewControllerへNavigateしてくれるようなものです。
StoryBoardを確認する
全体のStoryBoardは以下のようになっています。
NavigationVCを見る
以下のようなコードになっています。
import UIKit class NavVC: UINavigationController, PlayerVCDelegate { //MARK: Properties @IBOutlet var playerView: PlayerView! @IBOutlet var searchView: SearchView! @IBOutlet var settingsView: SettingsView! let titleLabel = UILabel() let names = ["Home", "Trending", "Subscriptions", "Account"] let hiddenOrigin: CGPoint = { let y = UIScreen.main.bounds.height - (UIScreen.main.bounds.width * 9 / 32) - 10 let x = -UIScreen.main.bounds.width let coordinate = CGPoint.init(x: x, y: y) return coordinate }() let minimizedOrigin: CGPoint = { let x = UIScreen.main.bounds.width/2 - 10 let y = UIScreen.main.bounds.height - (UIScreen.main.bounds.width * 9 / 32) - 10 let coordinate = CGPoint.init(x: x, y: y) return coordinate }() let fullScreenOrigin = CGPoint.init(x: 0, y: 0) //Methods func customization() { //NavigationBar buttons //Settings Button let settingsButton = UIButton.init(type: .system) settingsButton.setImage(UIImage.init(named: "navSettings"), for: .normal) settingsButton.tintColor = UIColor.white settingsButton.addTarget(self, action: #selector(self.showSettings), for: UIControlEvents.touchUpInside) self.navigationBar.addSubview(settingsButton) settingsButton.translatesAutoresizingMaskIntoConstraints = false let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .height, relatedBy: .equal, toItem: settingsButton, attribute: .height, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: settingsButton, attribute: .width, relatedBy: .equal, toItem: settingsButton, attribute: .height, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .centerY, relatedBy: .equal, toItem: settingsButton, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .right, relatedBy: .equal, toItem: settingsButton, attribute: .right, multiplier: 1.0, constant: 10).isActive = true //SearchButton let searchButton = UIButton.init(type: .system) searchButton.setImage(UIImage.init(named: "navSearch"), for: .normal) searchButton.tintColor = UIColor.white searchButton.addTarget(self, action: #selector(self.showSearch), for: UIControlEvents.touchUpInside) self.navigationBar.addSubview(searchButton) searchButton.translatesAutoresizingMaskIntoConstraints = false let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .height, relatedBy: .equal, toItem: searchButton, attribute: .height, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: searchButton, attribute: .width, relatedBy: .equal, toItem: searchButton, attribute: .height, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .centerY, relatedBy: .equal, toItem: searchButton, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: searchButton, attribute: .right, relatedBy: .equal, toItem: settingsButton, attribute: .left, multiplier: 1.0, constant: -10).isActive = true //TitleLabel setup self.titleLabel.font = UIFont.systemFont(ofSize: 18) self.titleLabel.textColor = UIColor.white self.titleLabel.text = self.names[0] self.navigationBar.addSubview(self.titleLabel) self.titleLabel.translatesAutoresizingMaskIntoConstraints = false let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .centerY, relatedBy: .equal, toItem: self.titleLabel, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .height, relatedBy: .equal, toItem: self.titleLabel, attribute: .height, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .left, relatedBy: .equal, toItem: self.titleLabel, attribute: .left, multiplier: 1.0, constant: -10).isActive = true self.titleLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true //NavigationBar color and shadow self.navigationBar.barTintColor = UIColor.rbg(r: 228, g: 34, b: 24) self.navigationBar.setBackgroundImage(UIImage(), for: .default) self.navigationBar.shadowImage = UIImage() self.navigationItem.hidesBackButton = true //SearchView setup self.view.addSubview(self.searchView) self.searchView.translatesAutoresizingMaskIntoConstraints = false let _ = NSLayoutConstraint.init(item: self.view, attribute: .top, relatedBy: .equal, toItem: self.searchView, attribute: .top, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .left, relatedBy: .equal, toItem: self.searchView, attribute: .left, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .right, relatedBy: .equal, toItem: self.searchView, attribute: .right, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .bottom, relatedBy: .equal, toItem: self.searchView, attribute: .bottom, multiplier: 1.0, constant: 0).isActive = true self.searchView.isHidden = true //SettingsView setup self.view.addSubview(self.settingsView) self.settingsView.translatesAutoresizingMaskIntoConstraints = false let _ = NSLayoutConstraint.init(item: self.view, attribute: .top, relatedBy: .equal, toItem: self.settingsView, attribute: .top, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .left, relatedBy: .equal, toItem: self.settingsView, attribute: .left, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .right, relatedBy: .equal, toItem: self.settingsView, attribute: .right, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .bottom, relatedBy: .equal, toItem: self.settingsView, attribute: .bottom, multiplier: 1.0, constant: 0).isActive = true self.settingsView.isHidden = true //PLayerView setup self.playerView.frame = CGRect.init(origin: self.hiddenOrigin, size: UIScreen.main.bounds.size) self.playerView.delegate = self //NotificaionCenter Setup NotificationCenter.default.addObserver(self, selector: #selector(self.changeTitle(notification:)), name: Notification.Name.init(rawValue: "scrollMenu"), object: nil) } @objc func showSearch() { self.searchView.alpha = 0 self.searchView.isHidden = false UIView.animate(withDuration: 0.2, animations: { self.searchView.alpha = 1 }) { _ in self.searchView.inputField.becomeFirstResponder() } } @objc func showSettings() { self.settingsView.isHidden = false self.settingsView.tableViewBottomConstraint.constant = 0 UIView.animate(withDuration: 0.3) { self.settingsView.backgroundView.alpha = 0.5 self.settingsView.layoutIfNeeded() } } @objc func changeTitle(notification: Notification) { if let info = notification.userInfo { let userInfo = info as! [String: CGFloat] self.titleLabel.text = self.names[Int(round(userInfo["length"]!))] } } func animatePlayView(toState: stateOfVC) { switch toState { case .fullScreen: UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 5, options: [.beginFromCurrentState], animations: { self.playerView.frame.origin = self.fullScreenOrigin }) case .minimized: UIView.animate(withDuration: 0.3, animations: { self.playerView.frame.origin = self.minimizedOrigin }) case .hidden: UIView.animate(withDuration: 0.3, animations: { self.playerView.frame.origin = self.hiddenOrigin }) } } func positionDuringSwipe(scaleFactor: CGFloat) -> CGPoint { let width = UIScreen.main.bounds.width * 0.5 * scaleFactor let height = width * 9 / 16 let x = (UIScreen.main.bounds.width - 10) * scaleFactor - width let y = (UIScreen.main.bounds.height - 10) * scaleFactor - height let coordinate = CGPoint.init(x: x, y: y) return coordinate } //MARK: Delegate methods func didMinimize() { self.animatePlayView(toState: .minimized) } func didmaximize(){ self.animatePlayView(toState: .fullScreen) } func didEndedSwipe(toState: stateOfVC){ self.animatePlayView(toState: toState) } func swipeToMinimize(translation: CGFloat, toState: stateOfVC){ switch toState { case .fullScreen: self.playerView.frame.origin = self.positionDuringSwipe(scaleFactor: translation) case .hidden: self.playerView.frame.origin.x = UIScreen.main.bounds.width/2 - abs(translation) - 10 case .minimized: self.playerView.frame.origin = self.positionDuringSwipe(scaleFactor: translation) } } //MARK: ViewController lifecycle override func viewDidLoad() { super.viewDidLoad() self.customization() } deinit { NotificationCenter.default.removeObserver(self) } override func viewDidAppear(_ animated: Bool) { super.viewWillAppear(true) if let window = UIApplication.shared.keyWindow { window.addSubview(self.playerView) } } }
1. Property
以下のようなプロパティを持っています。
//MARK: Properties @IBOutlet var playerView: PlayerView! @IBOutlet var searchView: SearchView! @IBOutlet var settingsView: SettingsView! let titleLabel = UILabel() let names = ["Home", "Trending", "Subscriptions", "Account"] let hiddenOrigin: CGPoint = { let y = UIScreen.main.bounds.height - (UIScreen.main.bounds.width * 9 / 32) - 10 let x = -UIScreen.main.bounds.width let coordinate = CGPoint.init(x: x, y: y) return coordinate }() let minimizedOrigin: CGPoint = { let x = UIScreen.main.bounds.width/2 - 10 let y = UIScreen.main.bounds.height - (UIScreen.main.bounds.width * 9 / 32) - 10 let coordinate = CGPoint.init(x: x, y: y) return coordinate }() let fullScreenOrigin = CGPoint.init(x: 0, y: 0)
また、これらの
playerView
searchView
settingsView
はsupporting View/
以下に定義されています。
ので、のちのちみます。
2. viewDidLoad()
ライフサイクルで大事なので、見ていきます。
以下のようになっています。
//MARK: ViewController lifecycle override func viewDidLoad() { super.viewDidLoad() self.customization() }
のようになっています。
customization()は以下のとおりです。
//Methods func customization() { //NavigationBar buttons //Settings Button let settingsButton = UIButton.init(type: .system) settingsButton.setImage(UIImage.init(named: "navSettings"), for: .normal) settingsButton.tintColor = UIColor.white settingsButton.addTarget(self, action: #selector(self.showSettings), for: UIControlEvents.touchUpInside) self.navigationBar.addSubview(settingsButton) settingsButton.translatesAutoresizingMaskIntoConstraints = false let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .height, relatedBy: .equal, toItem: settingsButton, attribute: .height, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: settingsButton, attribute: .width, relatedBy: .equal, toItem: settingsButton, attribute: .height, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .centerY, relatedBy: .equal, toItem: settingsButton, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .right, relatedBy: .equal, toItem: settingsButton, attribute: .right, multiplier: 1.0, constant: 10).isActive = true //SearchButton let searchButton = UIButton.init(type: .system) searchButton.setImage(UIImage.init(named: "navSearch"), for: .normal) searchButton.tintColor = UIColor.white searchButton.addTarget(self, action: #selector(self.showSearch), for: UIControlEvents.touchUpInside) self.navigationBar.addSubview(searchButton) searchButton.translatesAutoresizingMaskIntoConstraints = false let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .height, relatedBy: .equal, toItem: searchButton, attribute: .height, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: searchButton, attribute: .width, relatedBy: .equal, toItem: searchButton, attribute: .height, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .centerY, relatedBy: .equal, toItem: searchButton, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: searchButton, attribute: .right, relatedBy: .equal, toItem: settingsButton, attribute: .left, multiplier: 1.0, constant: -10).isActive = true //TitleLabel setup self.titleLabel.font = UIFont.systemFont(ofSize: 18) self.titleLabel.textColor = UIColor.white self.titleLabel.text = self.names[0] self.navigationBar.addSubview(self.titleLabel) self.titleLabel.translatesAutoresizingMaskIntoConstraints = false let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .centerY, relatedBy: .equal, toItem: self.titleLabel, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .height, relatedBy: .equal, toItem: self.titleLabel, attribute: .height, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.navigationBar, attribute: .left, relatedBy: .equal, toItem: self.titleLabel, attribute: .left, multiplier: 1.0, constant: -10).isActive = true self.titleLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true //NavigationBar color and shadow self.navigationBar.barTintColor = UIColor.rbg(r: 228, g: 34, b: 24) self.navigationBar.setBackgroundImage(UIImage(), for: .default) self.navigationBar.shadowImage = UIImage() self.navigationItem.hidesBackButton = true //SearchView setup self.view.addSubview(self.searchView) self.searchView.translatesAutoresizingMaskIntoConstraints = false let _ = NSLayoutConstraint.init(item: self.view, attribute: .top, relatedBy: .equal, toItem: self.searchView, attribute: .top, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .left, relatedBy: .equal, toItem: self.searchView, attribute: .left, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .right, relatedBy: .equal, toItem: self.searchView, attribute: .right, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .bottom, relatedBy: .equal, toItem: self.searchView, attribute: .bottom, multiplier: 1.0, constant: 0).isActive = true self.searchView.isHidden = true //SettingsView setup self.view.addSubview(self.settingsView) self.settingsView.translatesAutoresizingMaskIntoConstraints = false let _ = NSLayoutConstraint.init(item: self.view, attribute: .top, relatedBy: .equal, toItem: self.settingsView, attribute: .top, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .left, relatedBy: .equal, toItem: self.settingsView, attribute: .left, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .right, relatedBy: .equal, toItem: self.settingsView, attribute: .right, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .bottom, relatedBy: .equal, toItem: self.settingsView, attribute: .bottom, multiplier: 1.0, constant: 0).isActive = true self.settingsView.isHidden = true //PLayerView setup self.playerView.frame = CGRect.init(origin: self.hiddenOrigin, size: UIScreen.main.bounds.size) self.playerView.delegate = self //NotificaionCenter Setup NotificationCenter.default.addObserver(self, selector: #selector(self.changeTitle(notification:)), name: Notification.Name.init(rawValue: "scrollMenu"), object: nil) }
非常に長いのですが、やっていることは先ほどの
playerView
searchView
settingsView
の初期化みたいなものです。
また、ボタンであればそれぞれshowSearch
など関数が定義されています。
3. viewDidAppear()
以下のようになっています。
override func viewDidAppear(_ animated: Bool) { super.viewWillAppear(true) if let window = UIApplication.shared.keyWindow { window.addSubview(self.playerView) } }
です。
MainVC
MainVC
は以下のようになっています。
import UIKit class MainVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { //MARK: Properties @IBOutlet var tabBarView: TabBarView! @IBOutlet weak var collectionView: UICollectionView! var views = [UIView]() //MARK: Methods func customization() { self.view.backgroundColor = UIColor.rbg(r: 228, g: 34, b: 24) //CollectionView Setup self.collectionView.contentInset = UIEdgeInsetsMake(44, 0, 0, 0) self.collectionView.frame = CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: (self.view.bounds.height)) //TabbarView setup self.view.addSubview(self.tabBarView) self.tabBarView.translatesAutoresizingMaskIntoConstraints = false let _ = NSLayoutConstraint.init(item: self.view, attribute: .top, relatedBy: .equal, toItem: self.tabBarView, attribute: .top, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .left, relatedBy: .equal, toItem: self.tabBarView, attribute: .left, multiplier: 1.0, constant: 0).isActive = true let _ = NSLayoutConstraint.init(item: self.view, attribute: .right, relatedBy: .equal, toItem: self.tabBarView, attribute: .right, multiplier: 1.0, constant: 0).isActive = true self.tabBarView.heightAnchor.constraint(equalToConstant: 64).isActive = true //ViewController init let homeVC = self.storyboard?.instantiateViewController(withIdentifier: "HomeVC") let trendingVC = self.storyboard?.instantiateViewController(withIdentifier: "TrendingVC") let subscriptionsVC = self.storyboard?.instantiateViewController(withIdentifier: "SubscriptionsVC") let accountVC = self.storyboard?.instantiateViewController(withIdentifier: "AccountVC") let viewControllers = [homeVC, trendingVC, subscriptionsVC, accountVC] for vc in viewControllers { self.addChildViewController(vc!) vc!.didMove(toParentViewController: self) vc!.view.frame = CGRect.init(x: 0, y: 0, width: self.view.bounds.width, height: (self.view.bounds.height - 44)) self.views.append(vc!.view) } self.collectionView.reloadData() //NotificationCenter setup NotificationCenter.default.addObserver(self, selector: #selector(self.scrollViews(notification:)), name: Notification.Name.init(rawValue: "didSelectMenu"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.hideBar(notification:)), name: NSNotification.Name("hide"), object: nil) } @objc func scrollViews(notification: Notification) { if let info = notification.userInfo { let userInfo = info as! [String: Int] self.collectionView.scrollToItem(at: IndexPath.init(row: userInfo["index"]!, section: 0), at: .centeredHorizontally, animated: true) } } @objc func hideBar(notification: NSNotification) { let state = notification.object as! Bool self.navigationController?.setNavigationBarHidden(state, animated: true) } //MARK: Delegates func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.views.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) cell.contentView.addSubview(self.views[indexPath.row]) return cell } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize.init(width: self.collectionView.bounds.width, height: (self.collectionView.bounds.height + 22)) } func scrollViewDidScroll(_ scrollView: UIScrollView) { let scrollIndex = scrollView.contentOffset.x / self.view.bounds.width NotificationCenter.default.post(name: Notification.Name.init(rawValue: "scrollMenu"), object: nil, userInfo: ["length": scrollIndex]) } //MARK: ViewController lifecyle override func viewDidLoad() { super.viewDidLoad() self.customization() } deinit { NotificationCenter.default.removeObserver(self) } }
1. Property
また、以下のようなプロパティをを持っています。
//MARK: Properties @IBOutlet var tabBarView: TabBarView! @IBOutlet weak var collectionView: UICollectionView! var views = [UIView]()
tabBarView
:タブのメニューをcollectionView
:views
: Navigateするビューの集合
viewDidload()
これは他のものと一緒なので、省略させていただきます。
3. 各部品
3.1 SearchView
以下のようになっていますが、ビルドしてま動作しないので今回は見ないことにします。
import UIKit class SearchView: UIView, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate { //MARK: Properties @IBOutlet weak var inputField: UITextField! @IBOutlet weak var tableView: UITableView! var suggestions = [String]() //MARK: Methods func customization() { self.tableView.delegate = self self.tableView.dataSource = self self.inputField.delegate = self } @IBAction func hideSearchView(_ sender: Any) { self.inputField.text = "" self.suggestions.removeAll() self.tableView.isHidden = true self.inputField.resignFirstResponder() UIView.animate(withDuration: 0.2, animations: { self.alpha = 0 }) { _ in self.isHidden = true } } //MARK: Delegates func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.suggestions.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SearchCell cell.resultLabel.text = self.suggestions[indexPath.row] return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { self.inputField.text = self.suggestions[indexPath.row] let cell = tableView.cellForRow(at: indexPath) cell?.isSelected = false } func textFieldShouldReturn(_ textField: UITextField) -> Bool { self.hideSearchView(self) return true } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { guard let text = self.inputField.text else { self.suggestions.removeAll() self.tableView.isHidden = true return true } let netText = text.addingPercentEncoding(withAllowedCharacters: CharacterSet())! let url = URL.init(string: "https://api.bing.com/osjson.aspx?query=\(netText)")! let _ = URLSession.shared.dataTask(with: url, completionHandler: { [weak self] (data, response, error) in guard let weakSelf = self else { return } if error == nil { if let json = try? JSONSerialization.jsonObject(with: data!, options: .mutableContainers) { let data = json as! [Any] DispatchQueue.main.async { weakSelf.suggestions = data[1] as! [String] if weakSelf.suggestions.count > 0 { weakSelf.tableView.reloadData() weakSelf.tableView.isHidden = false } else { weakSelf.tableView.isHidden = true } } } } }).resume() return true } //MARK: View LifeCycle override func awakeFromNib() { super.awakeFromNib() self.customization() } } class SearchCell: UITableViewCell { @IBOutlet weak var resultLabel: UILabel! }
3.2 SettingView
設定ボタンを押すと以下のように出ます。
下から出てくるものがtableview
として定義されてあります。
import UIKit class SettingsView: UIView, UITableViewDelegate, UITableViewDataSource { //MARK: Properties @IBOutlet weak var tableView: UITableView! @IBOutlet weak var backgroundView: UIButton! @IBOutlet weak var tableViewBottomConstraint: NSLayoutConstraint! let items = ["Settings", "Terms & privacy policy", "Send Feedback", "Help", "Switch Account", "Cancel"] //MARK: Methods func customization() { self.tableView.delegate = self self.tableView.dataSource = self self.backgroundView.alpha = 0 self.tableViewBottomConstraint.constant = -self.tableView.bounds.height self.layoutIfNeeded() } @IBAction func hideSettingsView(_ sender: Any) { self.tableViewBottomConstraint.constant = -self.tableView.bounds.height UIView.animate(withDuration: 0.3, animations: { self.backgroundView.alpha = 0 self.layoutIfNeeded() }) { _ in self.isHidden = true } } //MARK: Delegates func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = self.items[indexPath.row] cell.imageView?.image = UIImage.init(named: self.items[indexPath.row]) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { self.hideSettingsView(self) } //MARK: View LifeCycle override func awakeFromNib() { super.awakeFromNib() self.customization() } }
3.3 PlayerView
以下のような窓のやつです。
まだこのレベルはたかそうだと思って、今回はスルーしました。
3.4 TabBarView
タブのビューで、横スクロールすると変わるものを定義しています。
import UIKit class TabBarView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate { //MARK: Properties @IBOutlet weak var collectionView: UICollectionView! @IBOutlet weak var whiteBar: UIView! @IBOutlet weak var whiteBarLeadingConstraint: NSLayoutConstraint! private let tabBarImages = ["home", "trending", "subscriptions", "account"] var selectedIndex = 0 //MARK: Methods func customization() { self.collectionView.delegate = self self.collectionView.dataSource = self self.backgroundColor = UIColor.rbg(r: 228, g: 34, b: 24) NotificationCenter.default.addObserver(self, selector: #selector(self.animateMenu(notification:)), name: Notification.Name.init(rawValue: "scrollMenu"), object: nil) } @objc func animateMenu(notification: Notification) { if let info = notification.userInfo { let userInfo = info as! [String: CGFloat] self.whiteBarLeadingConstraint.constant = self.whiteBar.bounds.width * userInfo["length"]! self.selectedIndex = Int(round(userInfo["length"]!)) self.layoutIfNeeded() self.collectionView.reloadData() } } //MARK: Delegates func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.tabBarImages.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! TabBarCellCollectionViewCell var imageName = self.tabBarImages[indexPath.row] if self.selectedIndex == indexPath.row { imageName += "Selected" } cell.icon.image = UIImage.init(named: imageName) return cell } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize.init(width: collectionView.bounds.width / 4, height: collectionView.bounds.height) } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if self.selectedIndex != indexPath.row { self.selectedIndex = indexPath.row NotificationCenter.default.post(name: Notification.Name.init(rawValue: "didSelectMenu"), object: nil, userInfo: ["index": self.selectedIndex]) } } //MARK: View LifeCycle override func awakeFromNib() { super.awakeFromNib() self.customization() } deinit { NotificationCenter.default.removeObserver(self) } } //TabBarCell Class class TabBarCellCollectionViewCell: UICollectionViewCell { @IBOutlet weak var icon: UIImageView! }
このような箇所にCollectionView
を使っています。
CollectionView以下の記事を参考にしました。
CollectionViewでしていること
**1.
4. まとめ
今回は学ぶことがかなり大きかったです。
というのも、あまりNavigationViewControllerを高度にカスタマイズした優良な記事は少ないので非常に勉強になりました。