Swift 一定以上スクロールしたらタブの位置を固定する(SwipeMenuViewControllerを使う)

やりたいことはこちらのページに書かれていたようなこと
https://techblog.zozo.com/entry/scroll_tab_page

https://github.com/yysskk/SwipeMenuViewController
SwipeMenuViewControllerというライブラリを使って試してみた

f:id:yoppy0066:20181207214729g:plain:w250

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をセットしなおしてるだけ


f:id:yoppy0066:20181207162401g:plain:w250

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


今回必要な各バージョン

nodenpmcordovaionic
v6.13.15.7.16.5.02.2.3
v8.10.05.7.16.5.03.2.0
v8.11.36.4.17.1.04.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。以上です