Swift 一定以上スクロールしたらタブの位置を固定する(SwipeMenuViewControllerを使う)
やりたいことはこちらのページに書かれていたようなこと
https://techblog.zozo.com/entry/scroll_tab_page
https://github.com/yysskk/SwipeMenuViewController
SwipeMenuViewControllerというライブラリを使って試してみた
ViewController.swift
import UIKit import SwipeMenuViewController class ViewController: UIViewController, SwipeMenuViewDelegate, SwipeMenuViewDataSource, UIScrollViewDelegate, TableViewControllerProtocol { var menus = ["メニュー1", "メニュー2", "メニュー3"] var header: UIView! var scrollView = UIScrollView() var swipeMenuView: SwipeMenuView! override func viewDidLoad() { super.viewDidLoad() // タブの上のView header = UIView(frame: CGRect(x: 10, y: 20, width: view.frame.width - 10*2, height: 200)) header.backgroundColor = UIColor.init(red: 157/255, green: 204/255, blue: 224/255, alpha: 1) scrollView.delegate = self scrollView.addSubview(header) // SwipeMenuView swipeMenuView = SwipeMenuView(frame: CGRect(x: 0, y: header.frame.origin.y + header.frame.height, width: view.frame.width, height: view.frame.height)) swipeMenuView.delegate = self swipeMenuView.dataSource = self scrollView.addSubview(swipeMenuView) var options: SwipeMenuViewOptions = .init() options.tabView.style = .segmented self.swipeMenuView.reloadData(options: options, default: nil, isOrientationChange: false) scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height) scrollView.contentSize = CGSize(width: view.frame.width, height: view.frame.height + 100) view.addSubview(scrollView) } // タブの数を返します func numberOfPages(in swipeMenuView: SwipeMenuView) -> Int { return menus.count } // タブのテキストを返します func swipeMenuView(_ swipeMenuView: SwipeMenuView, titleForPageAt index: Int) -> String { return menus[index] } // タブが選択されたときに表示するViewControllerを返します func swipeMenuView(_ swipeMenuView: SwipeMenuView, viewControllerForPageAt index: Int) -> UIViewController { let viewController = TableViewController() viewController.delegate = self addChild(viewController) return viewController } // テーブルをスクロールしたら呼ばれる // テーブルビューのスクロール位置からヘッダーのスクロール位置をセットする func receiveTableViewControllerScroll(_ offset: CGFloat) { let new = header.frame.origin.y + offset - 22 let max = header.frame.origin.y + header.frame.size.height - 22 let now = scrollView.contentOffset.y if new < max { scrollView.setContentOffset(CGPoint(x: 0, y: new), animated: false) } else if now < new { scrollView.setContentOffset(CGPoint(x: 0, y: max), animated: false) } } }
TableViewController.swift
import UIKit class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { weak var delegate: TableViewControllerProtocol? var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() tableView = UITableView(frame: CGRect(x:0, y:0, width:view.frame.width, height:view.frame.height)) tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") tableView.delegate = self tableView.dataSource = self tableView.showsVerticalScrollIndicator = false tableView.bounces = false self.view.addSubview(tableView) } private func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return 60 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 100 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath) cell.textLabel?.text = String(format: "行番号:%d", indexPath.row) return cell } // スクロール位置を親のViewControllerに通知 func scrollViewDidScroll(_ scrollView: UIScrollView) { delegate?.receiveTableViewControllerScroll(tableView.contentOffset.y) } } protocol TableViewControllerProtocol: class { func receiveTableViewControllerScroll(_ offset: CGFloat) -> Void }
課題
・行数が少ない場合にバグる
・ヘッダー部分をスクロールした場合に子のスクロール位置も調整するべき
だれか修正してソース共有してください。以上です
Swift UIViewを再描画
autolayout使わない方法メモ
と言ってもframeをセットしなおしてるだけ
import UIKit class ViewController: UIViewController { var wrapper: Wrapper! class Wrapper: UIView { var message = UILabel() required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override init(frame: CGRect) { super.init(frame: frame) self.setView() } func setView() { message.text = "未設定" message.font = UIFont.systemFont(ofSize: 20) message.numberOfLines = 0 addSubview(message) updateLayout() } func updateLayout() { let margin: CGFloat = 10 let size = message.sizeThatFits(CGSize(width: frame.width - margin*2, height: 0)) message.frame = CGRect(x: 0, y: margin, width: frame.width - margin*2, height: size.height) frame.size = CGSize(width: frame.width, height: message.frame.origin.y + message.frame.height + 10) } } override func viewDidLoad() { super.viewDidLoad() wrapper = Wrapper(frame: CGRect(x: 10, y: 30, width: view.frame.width - 10*2, height: 0)) wrapper.backgroundColor = UIColor.lightGray view.addSubview(wrapper) DispatchQueue.main.asyncAfter(deadline: .now() + 8) { self.wrapper.message.text = "1行目\n2行目\n3行目" self.wrapper.updateLayout() } } }
以上です
Swift UITableViewのセクションヘッダーをカスタマイズする方法メモ
やることは以下
・カスタムUIViewの実装
・viewForHeaderInSectionとheightForHeaderInSectionメソッドの定義
viewForHeaderInSectionはカスタムViewを返す
heightForHeaderInSectionはカスタムViewの高さを返す
テーブルセルのカスタマイズと考え方は同じ
カスタムUIViewの実装
class CustomeView { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override init(frame: CGRect) { super.init(frame: frame) } func setup(text: String) -> CGFloat { let width = UIScreen.main.bounds.size.width let label = UILabel(frame: CGRect(x: 0, y: 0, width: width: height: 100)) label.text = text addSubview(label) return label.frame.height } }
viewForHeaderInSectionとheightForHeaderInSectionメソッドの定義
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { ・・・ func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let header = CustomeHeaderView() let _ = header.setup(text: "...") return header } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { let header = CustomeHeaderView() return header.setup(text: "...") } ・・・ }
以上です
Swift4.2 UITableViewひな型
コピペ用
AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var navigationController: UINavigationController? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let viewController: ViewController = ViewController() navigationController = UINavigationController(rootViewController: viewController) self.window = UIWindow(frame: UIScreen.main.bounds) self.window?.rootViewController = navigationController self.window?.makeKeyAndVisible() return true } ・・・ }
ViewController.swift
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var tableView: UITableView! var tableData: [Dictionary<String,AnyObject>]! override func viewDidLoad() { super.viewDidLoad() // データを初期化します tableData = [Dictionary<String,AnyObject>]() // VIEWをセットします setView() } // VIEWをセットします func setView() { let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.height let displayWidth = self.view.frame.width let displayHeight = self.view.frame.height tableView = UITableView(frame: CGRect(x:0, y:statusBarHeight, width:displayWidth, height:displayHeight)) tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") tableView.delegate = self tableView.dataSource = self self.view.addSubview(tableView) } // テーブルセルの高さをかえします func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 60 } // テーブルの行数をかえします func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.tableData.count } // テーブルセルにデータをセットします func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath) return cell } // テーブルセル選択時の処理を記述します func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let viewController: DetailViewController = DetailViewController() self.navigationController?.pushViewController(viewController, animated: true) } }
DetailViewController.swift
import UIKit class DetailViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.white } }
以上
Swift 動画からキャプチャ画像を抽出する
動画ファイルから1秒単位でキャプチャ画像を生成するサンプルをメモしておく
import UIKit import AVKit class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { var picker = UIImagePickerController() var scrollView = UIScrollView() override func viewDidLoad() { super.viewDidLoad() scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height) view.addSubview(scrollView) // フォトライブラリーから動画を取得 picker.sourceType = UIImagePickerController.SourceType.photoLibrary picker.mediaTypes = ["public.movie"] picker.delegate = self present(picker, animated: true, completion: nil) } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { dismiss(animated: true, completion: nil) guard let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL else { return } var images = [UIImage]() let asset = AVAsset(url: videoURL) let duration = CMTimeGetSeconds(asset.duration) let generator = AVAssetImageGenerator(asset: asset) generator.appliesPreferredTrackTransform = true // 1秒毎にUIImageを生成 for index: Int in 0 ..< Int(duration) { let floatTime = Float64(index) let time = CMTimeMakeWithSeconds(floatTime, preferredTimescale: 600) if let image = try? generator.copyCGImage(at: time, actualTime: nil) { images.append(UIImage(cgImage: image)) } } // UIImageを画面に表示 var y: CGFloat = 0 let marginX: CGFloat = 50 let marginY: CGFloat = 10 for index: Int in 0 ..< images.count { let image = images[index] let width = view.frame.width - marginX*2 let ratio = width / image.size.width let height = image.size.height * ratio let imageView = UIImageView(image: image) imageView.frame = CGRect(x: marginX, y: y, width: width, height: height) scrollView.addSubview(imageView) scrollView.contentSize = CGSize(width: view.frame.width, height: y + height) y = y + height + marginY } } }
以上です
GitLab リポジトリ削除手順
たまにやろうとすると忘れるのでメモ
英語
1. プロジェクトのTOPへ遷移
2. 左メニューの「Settings」をクリック
3. Settings画面の「Advanced」(画面1番下)の「Expand」をクリック
4. 展開された画面の「Remove project」をクリック
5. 確認画面が表示されるのでプロジェクト名を入力して「Confirm」クリック
日本語
1. プロジェクトのTOPへ遷移
2. 左メニューの「設定」をクリック
3. 設定画面の「Advanced」(画面1番下)の「Expand」をクリック
4. 展開された画面の「Remove project」をクリック
5. 確認画面が表示されるのでプロジェクト名を入力して「Confirm」クリック
以上です
ndenv 使い方メモ
今さらだけどプロジェクトごとにnodeやら各ソフトのバージョンがバラバラなのが増えて来てnodebrewでいちいち切り替えるのもきつくなってきた。のでndenvいれたのでそのメモ。
ndenvインストール
すでにインストールされていないか確認してなければインストール
# 確認 $ brew list | grep ndenv # インストール $ brew install ndenv # インストールされたことを確認 $ ndenv --version ndenv 0.4.0-4-ga339097
.bash_profileにndenvの設定を追記
自分の場合はnodebrewを使っていたのでnodebrewの設定をコメントアウト
# nodebrew # export PATH=$HOME/.nodebrew/current/bin:$PATH # ndenv export PATH="$HOME/.ndenv/bin:$PATH" eval "$(ndenv init -)"
nodejsのPATHがndenvになっているか確認
$ source ~/.bash_profile $ which node /Users/xxxxx/.ndenv/shims/node
ndenv installを使えるように
$ git clone https://github.com/riywo/node-build.git $(ndenv root)/plugins/node-build
参考
https://qiita.com/noraworld/items/462689e108c10102d51f
今回必要な各バージョン
node | npm | cordova | ionic |
---|---|---|---|
v6.13.1 | 5.7.1 | 6.5.0 | 2.2.3 |
v8.10.0 | 5.7.1 | 6.5.0 | 3.2.0 |
v8.11.3 | 6.4.1 | 7.1.0 | 4.2.1 |
前準備
## インストール可能なバージョンを調べる $ ndenv install -l ## インストール済みのバージョンを調べる $ ndenv versions
v8.10.0の環境作成
## プロジェクト用のディレクトリ作成 $ mkdir v8_10_0 && cd $_ ## node v8.10.0のインストール $ ndenv install v8.10.0 ## このプロジェクトで使用するnodeのバージョン設定 $ ndenv local v8.10.0 $ node -v v8.10.0 ## npm 5.7.1をインストール $ npm install -g npm@5.7.1 $ npm -v 5.7.1 ## cordova 6.5.0をインストール $ npm install -g cordova@6.5.0 $ cordova -v 6.5.0 ## ionic 3.2.0をインストール $ npm install -g ionic@3.2.0 $ ionic -v 3.2.0
v6.13.1とv8.11.3についてもそれぞれ行う。
プロジェクトのディレクトリ直下に.node-versionがそれぞれ作成されるのでこのファイルをgitなどで管理して共有すればok。以上です