Swift・iOS

Swiftを中心に学んだことを記録に残すブログです。技術に関係ない記事もたまに書いています。

【Swift】Initialization Closureを使ってリファクタリング

【Swift】Initialization ClosureでviewDidLoadの肥大化を防ぐ - Qiitaを参考にさせていただき、過去の記事で使ったサンプルコードをリファクタリングしてみました。

※サンプルコードは【Swift】Swift4でHMSegmentedControlを試す - Swift・iOSを使っています。

 

■開発環境

Xcode 10.1

・Swift 4.2.1

 

■修正前

viewDidLoadにHMSegmentedControlの設定と追加、UIViewの設定と追加が3つ分実装されています。コードが肥大化し読みにくい状態です。。

・ViewController.swift

import UIKit
import HMSegmentedControl

class ViewController: UIViewController, UIScrollViewDelegate {
    // メニューを表示するView
    @IBOutlet weak var headerView: UIView!
    // メニューごとのViewを表示するScrollView
    @IBOutlet weak var scrollView: UIScrollView!
    
    // HMSegmentedControlをインスタンス化
    var segmentedControls: HMSegmentedControl!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // headerViewに合わせてsegmentedControlsのサイズを指定
        let statusbarHeight: CGFloat = UIApplication.shared.statusBarFrame.height
        self.segmentedControls = HMSegmentedControl(frame: CGRect(x: 0, y: statusbarHeight, width: self.view.frame.size.width, height:self.headerView.frame.size.height))
        // メニューのタイトル
        self.segmentedControls.sectionTitles = ["Menu1", "Menu2", "Menu3"]
        // インジケータのスタイルを指定
        self.segmentedControls.selectionIndicatorLocation =  HMSegmentedControlSelectionIndicatorLocation.down
        self.segmentedControls.selectionStyle = HMSegmentedControlSelectionStyle.fullWidthStripe
        // インジケータの色を指定
        self.segmentedControls.selectionIndicatorColor = UIColor(red: 30/255, green: 144/255, blue: 255/255, alpha: 1.0)
        // インジケータの高さ(太さ)を指定
        self.segmentedControls.selectionIndicatorHeight = 2.0
        // segmentedControlsで選択中の文字の色
        self.segmentedControls.selectedTitleTextAttributes = [
            NSAttributedString.Key.foregroundColor: UIColor(red: 30/255, green: 144/255, blue: 255/255, alpha: 1.0),
            NSAttributedString.Key.font: UIFont(name: "HiraginoSans-W6", size: 16)!
        ]
        // segmentedControlsで非選択中の文字の色
        self.segmentedControls.titleTextAttributes = [
            NSAttributedString.Key.foregroundColor: UIColor.lightGray,
            NSAttributedString.Key.font: UIFont(name: "HiraginoSans-W6", size: 16)!
        ]
        
        self.view.addSubview(segmentedControls)
        
        // メニューで選択したタイトルに合わせてScrollViewの表示を変更する
        let blockVariable: IndexChangeBlock =  {(index: Int) -> Void in
            let frame = CGRect(x: self.scrollView.frame.size.width * CGFloat(index), y: 0, width: self.scrollView.frame.size.width, height: self.scrollView.frame.size.height)
            self.scrollView.scrollRectToVisible(frame, animated: true)
        }
        segmentedControls.indexChangeBlock = blockVariable
        
        scrollView.delegate = self
        // メニュー単位のスクロールを可能にする
        scrollView.isPagingEnabled = true
        // 水平方向のスクロールインジケータを非表示にする
        scrollView.showsHorizontalScrollIndicator = false
        // scrollViewのサイズを指定(幅は1メニューに表示するViewの幅×メニュー数)
        scrollView.contentSize = CGSize(width: self.view.frame.size.width * 3, height: self.scrollView.frame.size.height)

        // 各メニューに表示するViewを生成
        let menu1 = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.scrollView.frame.size.height))
        menu1.backgroundColor = UIColor.cyan
        self.scrollView.addSubview(menu1)
        
        let menu2 = UIView(frame: CGRect(x: self.view.frame.size.width, y: 0, width: self.view.frame.size.width, height: self.scrollView.frame.size.height))
        menu2.backgroundColor = UIColor.red
        self.scrollView.addSubview(menu2)
        
        let menu3 = UIView(frame: CGRect(x: self.view.frame.size.width * 2, y: 0, width: self.view.frame.size.width, height: self.scrollView.frame.size.height))
        menu3.backgroundColor = UIColor.green
        self.scrollView.addSubview(menu3)
    }
    
    // ScrollViewの横スクロールに合わせてメニューのインジケータを移動する
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        segmentedControls.selectedSegmentIndex = Int(scrollView.contentOffset.x / scrollView.frame.maxX)
    }
}

 

■修正後

Initialization Closureを使ってHMSegmentedControlの設定をviewDidLoadから切り出しすことができました。また、UIViewもClosureによって各Viewの設定がまとまりコードの見通しがよくなったように感じます。

import UIKit
import HMSegmentedControl

class ViewController: UIViewController {
    // メニューを表示するView
    @IBOutlet weak var headerView: UIView!
    // メニューごとのViewを表示するScrollView
    @IBOutlet weak var scrollView: UIScrollView!
    
    private lazy var segmentedControls: HMSegmentedControl = {
        // headerViewに合わせてsegmentedControlsのサイズを指定
        let statusbarHeight: CGFloat = UIApplication.shared.statusBarFrame.height
        let segmentedControls = HMSegmentedControl(frame: CGRect(x: 0, y: statusbarHeight, width: self.view.frame.size.width, height:self.headerView.frame.size.height))
        // メニューのタイトル
        segmentedControls.sectionTitles = ["Menu1", "Menu2", "Menu3"]
        // インジケータのスタイルを指定
        segmentedControls.selectionIndicatorLocation =  HMSegmentedControlSelectionIndicatorLocation.down
        segmentedControls.selectionStyle = HMSegmentedControlSelectionStyle.fullWidthStripe
        // インジケータの色を指定
        segmentedControls.selectionIndicatorColor = UIColor(red: 30/255, green: 144/255, blue: 255/255, alpha: 1.0)
        // インジケータの高さ(太さ)を指定
        segmentedControls.selectionIndicatorHeight = 2.0
        // segmentedControlsで選択中の文字の色
        segmentedControls.selectedTitleTextAttributes = [
            NSAttributedString.Key.foregroundColor: UIColor(red: 30/255, green: 144/255, blue: 255/255, alpha: 1.0),
            NSAttributedString.Key.font: UIFont(name: "HiraginoSans-W6", size: 16)!
        ]
        // segmentedControlsで非選択中の文字の色
        segmentedControls.titleTextAttributes = [
            NSAttributedString.Key.foregroundColor: UIColor.lightGray,
            NSAttributedString.Key.font: UIFont(name: "HiraginoSans-W6", size: 16)!
        ]
        
        // メニューで選択したタイトルに合わせてScrollViewの表示を変更する
        let blockVariable: IndexChangeBlock =  {(index: Int) -> Void in
            let frame = CGRect(x: self.scrollView.frame.size.width * CGFloat(index), y: 0, width: self.scrollView.frame.size.width, height: self.scrollView.frame.size.height)
            self.scrollView.scrollRectToVisible(frame, animated: true)
        }
        segmentedControls.indexChangeBlock = blockVariable
        
        return segmentedControls
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        scrollView.delegate = self
        // メニュー単位のスクロールを可能にする
        scrollView.isPagingEnabled = true
        // 水平方向のスクロールインジケータを非表示にする
        scrollView.showsHorizontalScrollIndicator = false
        // scrollViewのサイズを指定(幅は1メニューに表示するViewの幅×メニュー数)
        scrollView.contentSize = CGSize(width: self.view.frame.size.width * 3, height: self.scrollView.frame.size.height)
        
        // segmentedControlsを追加
        self.view.addSubview(self.segmentedControls)
        
        // 各メニューに表示するViewを追加
        let menu1: UIView = {
            let menu1 = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.scrollView.frame.size.height))
            menu1.backgroundColor = UIColor.cyan
            return menu1
        }()
        self.scrollView.addSubview(menu1)
        
        let menu2: UIView = {
            let menu2 = UIView(frame: CGRect(x: self.view.frame.size.width, y: 0, width: self.view.frame.size.width, height: self.scrollView.frame.size.height))
            menu2.backgroundColor = UIColor.red
            return menu2
        }()
        self.scrollView.addSubview(menu2)
        
        let menu3: UIView = {
            let menu3 = UIView(frame: CGRect(x: self.view.frame.size.width * 2, y: 0, width: self.view.frame.size.width, height: self.scrollView.frame.size.height))
            menu3.backgroundColor = UIColor.green
            return menu3
        }()
        self.scrollView.addSubview(menu3)
    }
}

// ScrollViewの横スクロールに合わせてメニューのインジケータを移動する
extension ViewController: UIScrollViewDelegate {
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        segmentedControls.selectedSegmentIndex = Int(scrollView.contentOffset.x / scrollView.frame.maxX)
    }
}

  

■関連リンク

【Swift】Initialization ClosureでviewDidLoadの肥大化を防ぐ - Qiita