【Swift】UICircularProgressRingでタイマーを作る

どうも、ねこきち(@nekokichi1_yos2)です。

 

 今回は、UICircularProgressRing、で”プログレスバーを搭載したタイマー”を実装する。

github.com

 

(参考)

qiita.com

 

完成

 

 

解説

 

UICircularProgressRingでは、

  • UICircularProgressRing
  • UICircularTImerRing 

の2種類が用意されており、今回は後者を使う。

 

UICircularTimerRingをインスタンス化し、startTimer()、でタイマー処理を実装。

//0秒から10秒まで
timerRing.startTimer(to: 10) { (state) in
}
//1秒から10秒まで
timerRing.startTimer(from: 1, to: 10) { (state) in
}

 

startTimer内でタイマーの状態(state)別に処理を分岐できる。

switch state {
case .finished: //タイマー終了時
case .continued: //タイマー再開
case .paused: //タイマー停止
}

 

タイマーの状態(起動前/実行中/停止中)を検知するために、変数flagを用意。

var flag = 0

 
タイマーを各状態へ移行するには、下記のメソッド。

.continueTimer()はタイマーが停止の状態でないと無反応。

//ストップ
timerRing.pauseTimer()
//再開
timerRing.continueTimer()
//最初の状態にリセット
timerRing.resetTimer()

 

今回の実装は、変数flagで

  • 0:タイマー起動前
  • 1:タイマー実行中
  • 2:タイマー停止

と仮定して、ボタンを押すごとに

  • 開始→ストップ→再開→ストップ→開始

と処理を変化させている。

 

ちなみに、outerRingColor、を未設定にした状態。

f:id:nekokichi_yos2:20200429180753p:plain

 

ソースコード

 

import UIKit
import UICircularProgressRing

class UCRViewController: UIViewController {
    
    @IBOutlet weak var start: UIButton!
    
    //UICircularTimerRing
    let timerRing = UICircularTimerRing()
    //プログレスバーの状態を示した数値
    var flag = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        setting()
    }
    
    @IBAction func start(_ sender: Any) {
        switch flag {
        case 0:
            self.flag = 1
            self.start.setTitle("ストップ", for: .normal)
            timerRing.startTimer(to: 5) { state in
                switch state {
                case .finished:
                    self.flag = 0
                    self.start.setTitle("開始", for: .normal)
                    //リセット
                    self.timerRing.resetTimer()
                case .continued:
                    self.flag = 1
                    self.start.setTitle("ストップ", for: .normal)
                case .paused:
                    self.flag = 2
                    self.start.setTitle("再開", for: .normal)
                }
            }
        case 1:
            //ストップ
            timerRing.pauseTimer()
        case 2:
            //再開
            timerRing.continueTimer()
        default:break
        }
    }
    
    //プログレスバーの設定
    func setting()  {
        //幅と高さ
        timerRing.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        //中央に設置
        timerRing.center = self.view.center
        //経過時間を示すテキストの表示
        timerRing.shouldShowValueText = true
        //バーの外側の色
        timerRing.outerRingColor = .clear
        //バーの色
        timerRing.innerRingColor = .blue
        self.view.addSubview(timerRing)
    }
    
}

【Swift】SideMenuでサイドメニューを作る

どうも、ねこきち(@nekokichi1_yos2)です。

 

今回は多くのアプリで使われている、サイドバー/サイドメニュー、を下記のライブラリで実装します。

github.com

 

(参考)

qiita.com

kimagureneet.hatenablog.com

解説

 

準備 

 

まず、

  • 3つのファイルを作る(メニュー表示/設定用、メニュー項目用(TableViewControllerじゃないとダメ))
  • ストーリーボードで2つのビューコントローラを設置(ViewController、NavigationController)
  • 各ビューコントローラのクラスとモジュールを設定

を行います。

 

今回の実装はストーリーボードを活用してます。

 

実は、SideMenuを解説してる記事のほとんどはコードでの実装なんですが、記事通りに実装しても最終的にはエラーなどが原因で無理でした。

 

なので、SideMenuの公式マニュアルと同じく、ストーリーボードでの実装にしました。

 

実装 - ViewController

 

先ほど追加したTableViewControllerをインスタンス化。

//サイドメニュー用のtableViewを生成
let menuViewController = TableViewController()

 

SideMenuのナビゲーションコントローラを生成。

rootViewControllerには、TableViewController()を持ったmenuViewControllerを指定。

//サイドメニューのナビゲーションコントローラを生成
let menuNavigationController = SideMenuNavigationController(rootViewController: menuViewController)

 

ナビゲーションコントローラをSideMenuのメニューとして追加。

スワイプ動作の追加も、UISwipeGestureRecognizer、を用意しなくても、SideMenu側で実装可能。

//左,右のメニューとして追加
SideMenuManager.default.leftMenuNavigationController = menuNavigationController
SideMenuManager.default.rightMenuNavigationController = menuNavigationController
//左、右スワイプジェスチャーを追加
SideMenuManager.default.addScreenEdgePanGesturesToPresent(toView: self.view, forMenu: .left)
SideMenuManager.default.addScreenEdgePanGesturesToPresent(toView: self.view, forMenu: .right)

 

SideMenu側でメニューの設定を行う。

他にも色々な設定値があるが、基本的にはこれだけで十分。

    //サイドメニューの設定
    private func makeSettings() -> SideMenuSettings {
        var settings = SideMenuSettings()
        //動作を指定
        settings.presentationStyle = .menuSlideIn
        //メニューの陰影度
        settings.presentationStyle.onTopShadowOpacity = 1.0
        //ステータスバーの透明度
        settings.statusBarEndAlpha = 0
        return settings
    }
    

 

「onTopShadowOpacity」

影を付ければ、立体感が出る。

f:id:nekokichi_yos2:20200429133216p:plain

 

「statusBarEndAlpha」

初期値は1で、ステータスバーが黒くなる。

f:id:nekokichi_yos2:20200429133110p:plain

 

実装 - TableViewController

 

特にSideMenu関連のコードは必要なし。

 

ただ、カスタムセルを登録しないと、エラーが出てしまう。

'unable to dequeue a cell with identifier cell - must register a nib or a class 
for the identifier or connect a prototype cell in a storyboard'

 

とりあえず、登録しておくだけでOK。

tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier:  "customcell")

 

ストーリーボード

 

f:id:nekokichi_yos2:20200429124952p:plain

 

f:id:nekokichi_yos2:20200429124220p:plain

 

「ViewController」

f:id:nekokichi_yos2:20200429124326p:plain

「NavigationController」

f:id:nekokichi_yos2:20200429124330p:plain

「RootViewController」

f:id:nekokichi_yos2:20200429124333p:plain

 

ソースコード

 

import UIKit
import SideMenu

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        //サイドメニュー用のtableViewを生成
        let menuViewController = TableViewController()
        //サイドメニューのナビゲーションコントローラを生成
        let menuNavigationController = SideMenuNavigationController(rootViewController: menuViewController)
        //設定を追加
        menuNavigationController.settings = makeSettings()
        //左,右のメニューとして追加
        SideMenuManager.default.leftMenuNavigationController = menuNavigationController
        SideMenuManager.default.rightMenuNavigationController = menuNavigationController
        //左、右スワイプジェスチャーを追加
        SideMenuManager.default.addScreenEdgePanGesturesToPresent(toView: self.view, forMenu: .left)
        SideMenuManager.default.addScreenEdgePanGesturesToPresent(toView: self.view, forMenu: .right)
    }
    
    //サイドメニューの設定
    private func makeSettings() -> SideMenuSettings {
        var settings = SideMenuSettings()
        //動作を指定
        settings.presentationStyle = .menuSlideIn
        //メニューの陰影度
        settings.presentationStyle.onTopShadowOpacity = 1.0
        //ステータスバーの透明度
        settings.statusBarEndAlpha = 0
        return settings
    }
    
}     let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel!.text = num[indexPath.row]
        return cell
    }
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        //メインスレッドでtableViewを更新
        DispatchQueue.main.async {
            if indexPath.row == self.num.count - 5 {
                self.num.append(contentsOf: self.num)
                tableView.reloadData()
            }
        }
    }
    
}

 

import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.separatorStyle = .none
        tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier:  "customcell")
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "メニュー"
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "customcell", for: indexPath)
        cell.textLabel?.text = "\(indexPath.row)"
        cell.textLabel?.textAlignment = .center
        return cell
    }

}

 

結果

 

f:id:nekokichi_yos2:20200429124532p:plainf:id:nekokichi_yos2:20200429124536p:plain

【Swift】tableViewで無限スクロールを実装してみた

どうも、ねこきち(@nekokichi1_yos2)です。

 

今回は、tableViewで無限スクロール、を実装。

 

解説

 

仕組みは

  1. スクロール開始
  2. (num.count -10)番目の要素が表示された
  3. numにnumの全要素を追加
  4. スクロール終了

 

スクロール時のnumの要素とセルの追加処理は、willDisplaycell、で行います。

 

セルを表示する前に呼び出されるので、問題なく処理が通ります。

 

ただ、scrollViewDidScroll()内でも追加処理を実行できますが、重複して処理が行われてしまうので、推奨はされてません。

orangelog.site

 

追加処理後のリロード処理をそのまま実行しても、スクロール動作と追加処理が原因なのか、少し動作がもっさりします。

 

しかし、DispatchQueueでメインスレッドに登録すれば、スムーズに動作します。

stackoverflow.com

 

ちなみに、途中でnumの要素を一部削除しようとしても、cellForRowAtでエラーが出る..。なんでだろう..。

 

ソースコード

 

import UIKit

class InfiniteScroll_tableView: UIViewController,UITableViewDelegate,UITableViewDataSource {
    
    @IBOutlet weak var tableView: UITableView!
    
    var num = [String]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
        //1~20の数字を代入
        for i in 1...10 {
            num.append("\(i)")
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return num.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel!.text = num[indexPath.row]
        return cell
    }
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        //メインスレッドでtableViewを更新
        DispatchQueue.main.async {
            if indexPath.row == self.num.count - 10 {
                self.num.append(contentsOf: self.num)
                tableView.reloadData()
            }
        }
    }
    
}

 

結果

 

【Swift】UI部品を設置する時のテンプレ

どうも、ねこきち(@nekokichi1_yos2)です。

 

Xcodeに内蔵されてるUI部品、ライブラリで生成するUI部品、構成や扱いが異なっても、UIViewに設置する工程は変わりません。

 

生成から設置までの流れは、

  1. 生成(let 変数 = UI部品)
  2. 位置(変数.frame = CGRect(....))
  3. 設定(.color、.text、.addTargetなど)
  4. 設置(self.view.addSubView...)

大体、こんな感じです。

 

もしライブラリで独自のUI部品を扱うときでも、工程さえ知ってれば、焦ることもありません。

 

import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let button = UIButton()
        button.frame = CGRect(x: 40, y: 200, width: 300, height: 50)
        button.setTitle("ボタン", for: .normal)
        button.backgroundColor = .green
        self.view.addSubview(button)
    }
    
}