カテゴリー
iOS Swift

UITableViewを使ってカスタムPickerViewのようなものを作る

Sample Code

class ViewController: UIViewController {

  let dataCount = 10 // this will be replaced by the datasource count.
  
  var currentIndex: Int = 0 {
    didSet {
      print("aboveButton.isHidden: \(aboveButton.isHidden)")

      if currentIndex == 0 {
        UIView.animate(withDuration: 1, animations: {
          self.aboveButton.alpha = 0
        })
      } else if currentIndex == dataCount - 1 {
        UIView.animate(withDuration: 1, animations: {
          self.belowButton.alpha = 0
        })
      }
      else {
        UIView.animate(withDuration: 0.5, animations: {
          self.aboveButton.alpha = 1
          self.belowButton.alpha = 1
        })
      }
    }
  }
  
  let tableViewHeight: CGFloat = 300
  
  let buttonSize: CGFloat = 50
  
  lazy var aboveButton: UIButton = {
    let button = UIButton()
    button.setImage(UIImage(systemName: "chevron.up"), for: .normal)
    button.imageView?.contentMode = .scaleAspectFit
    button.contentHorizontalAlignment = .fill
    button.contentVerticalAlignment = .fill
    button.tintColor = .systemGray
    button.addTarget(self, action: #selector(onAboveButtonTapped), for: .touchUpInside)
    return button
  }()
  
  @objc private func onAboveButtonTapped() {
    let index = currentIndex - 1
    guard index >= 0 else { return }
    let indexPath = IndexPath(row: index, section: 0)
    tableView.scrollToRow(at: indexPath, at: .top, animated: true)
    currentIndex = index
  }
  
  lazy var belowButton: UIButton = {
    let button = UIButton()
    button.setImage(UIImage(systemName: "chevron.down"), for: .normal)
    button.imageView?.contentMode = .scaleAspectFit
    button.contentHorizontalAlignment = .fill
    button.contentVerticalAlignment = .fill
    button.tintColor = .systemGray
    button.addTarget(self, action: #selector(onBelowButtonTapped), for: .touchUpInside)
    return button
  }()
  
  @objc private func onBelowButtonTapped() {
    let index = currentIndex + 1
    guard index <= dataCount - 1 else { return }
    let indexPath = IndexPath(row: index, section: 0)
    tableView.scrollToRow(at: indexPath, at: .top, animated: true)
    currentIndex = index
  }
  
  lazy var tableView: UITableView = {
    let tableView = UITableView()
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    tableView.separatorColor = .clear
    tableView.rowHeight = tableViewHeight
    return tableView
  }()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    view.backgroundColor = .white
    view.addSubview(tableView)
    view.addSubview(aboveButton)
    view.addSubview(belowButton)
    if currentIndex == 0 {
      aboveButton.alpha = 0
    }
    if currentIndex == dataCount - 1 {
      belowButton.alpha = 0
    }
    tableView.delegate = self
    tableView.dataSource = self
  }
  
  override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    layout()
    
  }
  
  private func layout() {
    tableView.frame = CGRect(x: 0, y: view.bounds.height/2 - tableViewHeight/2, width: view.bounds.width, height: tableViewHeight)
    aboveButton.frame = CGRect(x: view.bounds.width/2 - buttonSize/2, y: tableView.frame.minY + 10, width: buttonSize, height: buttonSize)
    belowButton.frame = CGRect(x: view.bounds.width/2 - buttonSize/2, y: tableView.frame.minY + tableViewHeight - 10 - buttonSize, width: buttonSize, height: buttonSize)
  }

}

extension ViewController: UITableViewDelegate {
  
}

extension ViewController: UITableViewDataSource {
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    dataCount
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    cell.textLabel?.text = "\(indexPath.row)"
    cell.backgroundColor = .systemGray6
    return cell
  }
  
}

extension ViewController: UIScrollViewDelegate {
  func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    var visibleRect = CGRect()
    visibleRect.origin = tableView.contentOffset
    visibleRect.size = tableView.bounds.size
    let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
    guard let indexPath = tableView.indexPathForRow(at: visiblePoint) else { return }
    currentIndex = indexPath.row
    tableView.scrollToRow(at: indexPath, at: .top, animated: true)
  }
  
  func scrollViewWillEndDragging(_ scrollView: UIScrollView,
                                 withVelocity velocity: CGPoint,
                                 targetContentOffset: UnsafeMutablePointer<CGPoint>)
  {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
      self.scrollViewDidEndDecelerating(scrollView)
    }
  }
}