migrate swift4.0 to swift4.2

swift4.0 から swift4.2 への修正内容メモしておく

UIKeyboardWillShow

UIResponder.keyboardWillShowNotification

UIKeyboardWillHide

UIResponder.keyboardWillHideNotification

UIControlEvents

UIControl.Event

UIEdgeInsetsInsetRect(rect, padding)

rect.inset(by: padding)

UIViewContentMode

UIView.ContentMode

addChildViewController

addChild

UIControlContentHorizontalAlignment

UIControl.ContentHorizontalAlignment

UITextBorderStyle

UITextField.BorderStyle

bringSubview(toFront: ***)

bringSubviewToFront(***)

UIAlertControllerStyle

UIAlertController.Style.alert

UIAlertActionStyle

UIAlertAction.Style

UIKeyboardFrameEndUserInfoKey

UIResponder.keyboardFrameEndUserInfoKey

UIActivityIndicatorViewStyle

UIActivityIndicatorView.Style

UITableViewCellStyle

UITableViewCell.CellStyle

UIImagePickerControllerSourceType

UIImagePickerController.SourceType

NSNotification.Name.UITextViewTextDidChange

UITextField.textDidChangeNotification

UIDatePickerMode

UIDatePicker.Mode

UIKeyboardFrameEndUserInfoKey

UIResponder.keyboardFrameEndUserInfoKey

popupViewController.didMove(toParentViewController: self)

popupViewController.didMove(toParent: self

UIApplicationLaunchOptionsKey

UIApplication.LaunchOptionsKey

NSAttributedStringKey

NSAttributedString.Key

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])

UIImagePickerControllerImageURL

UIImagePickerController.InfoKey.imageURL

以上です

Swift LUExpandableTableViewを使って折りたたみ可能なUITableViewを実装

こんな感じのありがちなUI。ライブラリを使わずに自前で実装する方法もけっこう書いていただいている人がいて実装することはできたのだが、テーブルのレコード数が増えると開閉したときのスクロール位置がガタガタ。

自分の実装方法に問題があるかもしれないが細かいところまで解決できずいたところにこちらのライブラリを見つけた。少し同じ問題は発生するのだが自分でやるよりは全然マシでこちらを使うことにしたので使い方をメモしておく。

https://github.com/LaurentiuUngur/LUExpandableTableView


f:id:yoppy0066:20181207220625g:plain

ViewController.swift

import UIKit
import LUExpandableTableView

class ViewController: UIViewController, LUExpandableTableViewDataSource, LUExpandableTableViewDelegate {

    var expandableTableView = LUExpandableTableView()

    override func viewDidLoad() {
	super.viewDidLoad()

        expandableTableView.frame = view.bounds
        view.addSubview(expandableTableView)

        expandableTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
	expandableTableView.register(CustomHeader.self, forHeaderFooterViewReuseIdentifier: "CustomeHeader")

	expandableTableView.expandableTableViewDelegate = self
	expandableTableView.expandableTableViewDataSource = self
    }

    // MARK: - LUExpandableTableViewDelegate
    // セルの高さを返します
    func expandableTableView(_ expandableTableView: LUExpandableTableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
	return 50
    }

    // セクションヘッダーの高さを返します
    func expandableTableView(_ expandableTableView: LUExpandableTableView, heightForHeaderInSection section: Int) -> CGFloat {
	let header = CustomHeader()
	return header.setup(text: "セクションタイトル")
    }

    // MARK: - LUExpandableTableViewDataSource
    // セクション数を返します
    func numberOfSections(in expandableTableView: LUExpandableTableView) -> Int {
	return 100
    }

    // セル数を返します
    func expandableTableView(_ expandableTableView: LUExpandableTableView, numberOfRowsInSection section: Int) -> Int {
	return 10
    }

    // View(セル)を返します
    func expandableTableView(_ expandableTableView: LUExpandableTableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = expandableTableView.dequeueReusableCell(withIdentifier: "Cell") else {
            return UITableViewCell()
        }
        cell.textLabel?.text = "セル"
        return cell
    }

    // View(セクション)を返します
    func expandableTableView(_ expandableTableView: LUExpandableTableView, sectionHeaderOfSection section: Int) -> LUExpandableTableViewSectionHeader {
        guard let header = expandableTableView.dequeueReusableHeaderFooterView(withIdentifier: "CustomeHeader") as? CustomHeader else {
            return LUExpandableTableViewSectionHeader()
        }
        let _ = header.setup(text: "セクション")
        return header
    }
}

CustomeHeader.swift

class CustomHeader: LUExpandableTableViewSectionHeader {

    var label = UILabel()
    var button = UIButton()

    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var isExpanded: Bool {
        didSet {
            self.button.setTitle(isExpanded ? "閉じる" : "開く", for: .normal)
        }
    }

    func setup(text: String) -> CGFloat {

        backgroundView = UIImageView()
        backgroundView?.backgroundColor = UIColor.init(red: 157/255, green: 204/255, blue: 224/255, alpha: 1)

        label.frame = CGRect(x: 10, y: 0, width:UIScreen.main.bounds.size.width*0.8,  height: 100)
        label.text = text
	label.font = UIFont.boldSystemFont(ofSize: 13)
        addSubview(label)

        button.frame = CGRect(x: label.frame.width, y: 0, width: UIScreen.main.bounds.size.width*0.2, height: 100)
        button.setTitle(isExpanded ? "閉じる" : "開く", for: .normal)
        button.setTitleColor(UIColor.black, for: .normal)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 13)
        button.addTarget(self, action: #selector(onOpen(sender:)), for: .touchUpInside)
        addSubview(button)

        return button.frame.origin.y + button.frame.height
    }

    @objc func onOpen(sender: UIButton) {
        delegate?.expandableSectionHeader(self, shouldExpandOrCollapseAtSection: section)
    }
}

以上です

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
        }
    }
}

以上です