やりたいこと
swiftでアプリ開発をしています。その中で、UITabBarをカスタマイズしたいと考えています。
まず、タブバーアイテムがスクロールしています。
そして、薄い縦線のようなもので区切られた一番右のタブバーアイテムは、
そのスクロールに干渉されていないような作りになっています。
また、分かりづらいですが、一番左のタブバーアイテムはある閾値を超えるとスクロールについていくようになっています。
1 TabBarの中で薄い縦線のようなものでエリアを区切る方法
2 TabBarItemを横スクロールする方法
を探しています。
実装方法をご存じの方いましたら、教えていただきたいです。
よろしくお願いします。
開発環境
Xcode 12.4
Mac 10.15.7
iOS 14.4
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/02/17 14:20
回答1件
0
ベストアンサー
イメージキャプチャから想像するに、GoodNotes 5 のようなインタフェースを考えているんだと思います。
そのアプリは使ったことはありませんが、いくつかの動画をみると、スライド可能なアイテムバーから構成されている感じです。
たぶん、ご希望の実装はこんな感じになるのではないでしょうか。
あくまでも動画から得られる情報だけで再現しています。
具体的には、左右に UIButton
, 区切りは背景色のある UIView
、アイテムバーの部分は UIScrollView
で構成しています。
StoryBoard や AutoLayout を使うと記述が増えるので、絶対値指定で作っていますが、たぶん標準的なデバイスであれば大丈夫だと思います。
完全に興味と即興で作ったので高度なことは期待していただきたくありませんが、おそらく基本的な要素は揃っていると思います。
概要はコメント中に記入していますので、詳しくはそちらを参考にしていただけますでしょうか。
Swift
1import UIKit 2 3// Scroll View に表示するカスタムセル 4class ItemCell: UICollectionViewCell { 5 private var image: UIImageView! 6 7 override init(frame: CGRect) { 8 super.init(frame: frame) 9 setup() 10 } 11 12 required init?(coder: NSCoder) { 13 fatalError("init(coder:) has not been implemented") 14 } 15 16 private func setup() { 17 image = UIImageView(frame: CGRect(x: 0, 18 y: 0, 19 width: self.frame.width, 20 height: self.frame.height)) 21 self.contentView.addSubview(image) 22 } 23 24 func setImage(icon: UIImage) { 25 image.image = icon 26 } 27} 28 29class ViewController: UIViewController { 30 var barItems = (10...20).map { String($0) } 31 32 // 上部の背景 33 lazy var baseView:UIView = { 34 let bv = UIView() 35 bv.backgroundColor = .systemTeal 36 return bv 37 }() 38 39 // 左ボタン 40 lazy var leftButton: UIButton = { 41 let bt = UIButton(type: .system) 42 bt.imageView?.contentMode = .scaleAspectFit 43 bt.contentHorizontalAlignment = .fill 44 bt.contentVerticalAlignment = .fill 45 bt.isEnabled = false 46 return bt 47 }() 48 49 //右ボタン 50 lazy var rightButton: UIButton = { 51 let bt = UIButton(type: .system) 52 bt.setTitle("Button", for: .normal) 53 return bt 54 }() 55 56 // 左区切り 57 lazy var leftSeparator:UIView = { 58 let bv = UIView() 59 bv.backgroundColor = .gray 60 return bv 61 }() 62 63 // スクロールビュー 64 lazy var itemBarCollectionView: UICollectionView = { 65 let layout = UICollectionViewFlowLayout() 66 layout.itemSize = CGSize(width: 50, height: 50) 67 layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) 68 layout.scrollDirection = .horizontal 69 let sv = UICollectionView(frame: .zero, collectionViewLayout: layout) 70 sv.dataSource = self 71 sv.delegate = self 72 sv.isScrollEnabled = true 73 sv.backgroundColor = .clear 74 return sv 75 }() 76 77 // 右区切り 78 lazy var rightSeparator:UIView = { 79 let bv = UIView() 80 bv.backgroundColor = .gray 81 return bv 82 }() 83 84 override func viewDidLoad() { 85 super.viewDidLoad() 86 87 self.view.backgroundColor = .white 88 self.view.addSubview(baseView) 89 90 baseView.addSubview(leftButton) 91 baseView.addSubview(leftSeparator) 92 baseView.addSubview(itemBarCollectionView) 93 baseView.addSubview(rightSeparator) 94 baseView.addSubview(rightButton) 95 96 leftButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) 97 rightButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) 98 99 itemBarCollectionView.register(ItemCell.self, forCellWithReuseIdentifier: "ItemCell") 100 } 101 102 @objc func buttonTapped(_ sender: UIButton) { 103 if sender == leftButton { 104 let item = sender.tag + 10 105 print("左ボタンが押された(アイテム番号(item))") 106 } else { 107 print("右ボタンが押された") 108 } 109 } 110 111 override func viewWillLayoutSubviews() { 112 super.viewWillLayoutSubviews() 113 let topInset = self.view.safeAreaInsets.top 114 let edgeInset: CGFloat = 8 115 let height: CGFloat = 50 116 let width = height 117 let screenWidth = self.view.frame.width 118 119 baseView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: height + topInset) 120 leftButton.frame = CGRect(x: edgeInset, y: topInset, width: height, height: height) 121 leftSeparator.frame = CGRect(x: edgeInset + width + 2, 122 y: topInset + 4, 123 width: 1, height: height - 8) 124 itemBarCollectionView.frame = CGRect(x: edgeInset + width + 5, y: topInset, 125 width: screenWidth - (width + edgeInset + 5) * 2, 126 height: height) 127 rightSeparator.frame = CGRect(x: screenWidth - (width + edgeInset + 1), 128 y: topInset + 4, 129 width: 1, height: height - 8) 130 rightButton.frame = CGRect(x: screenWidth - (width + edgeInset), 131 y: topInset, 132 width: width, height: height) 133 } 134} 135 136extension ViewController: UICollectionViewDataSource { 137 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 138 return barItems.count 139 } 140 141 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 142 let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ItemCell", for: indexPath) as! ItemCell 143 144 cell.setImage(icon: UIImage(systemName: barItems[indexPath.row] + ".circle")!) 145 146 return cell 147 } 148} 149 150extension ViewController: UICollectionViewDelegate { 151 // 自動スクロールが終了した場合 152 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 153 print(#function) 154 155 snap(scrollView) 156 } 157 158 // ドラッグが終了した場合 159 func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 160 print(#function) 161 162 // decelerate == true の場合は、自動スクロールが続いている状態 163 // その場合は、自動スクロールが終了した時点で scrollViewDidEndDecelerating(_:)が呼ばれる 164 if decelerate == false { 165 // ドラッグ終了の場所で止めた場合 166 snap(scrollView) 167 } 168 } 169 170 private func snap(_ scrollView: UIScrollView) { 171 let height = Int(scrollView.frame.height) 172 let contentWidth = Int(scrollView.contentSize.width) 173 let padding = contentWidth / barItems.count - Int(height) 174 175 var item = Int(scrollView.contentOffset.x) / (50 + padding) 176 177 // アイコンの前半部分でスクロールが終わった場合には、そのアイコンを全て表示 178 // 後半の場合には、次のアイコンに移動させる 179 if Int(scrollView.contentOffset.x) % (height + padding) < height / 2 { 180 UIView.animate(withDuration: 0.5) { 181 scrollView.contentOffset.x = CGFloat((height + padding) * item) 182 } 183 } else { 184 item += 1 185 UIView.animate(withDuration: 0.5){ 186 scrollView.contentOffset.x = CGFloat((height + padding) * item) 187 } 188 } 189 190 // 今回は、item の番号が奇数の時だけ左ボタンにアイテムを設定している 191 if item > 0 && item % 2 == 1 { 192 leftButton.setImage(UIImage(systemName: barItems[item] + ".circle")!, for: .normal) 193 // tag 番号を使って選択したボタンの番号を伝える 194 leftButton.tag = item 195 leftButton.isEnabled = true 196 } else if item == 0 { 197 leftButton.setImage(nil, for: .normal) 198 leftButton.isEnabled = false 199 } 200 } 201}
追記
とある方からのコメントでちょっと変更を加えてみました。
Swift
1 // 今回は、item の番号が奇数の時だけ左ボタンにアイテムを設定している 2 if item > 0 && item % 2 == 1 { 3 UIView.transition(with: leftButton, duration: 0.4, options: .transitionCrossDissolve) { 4 self.leftButton.setImage(UIImage(systemName: self.barItems[item] + ".circle")!, for: .normal) 5 } completion: { _ in 6 self.leftButton.tag = item 7 self.leftButton.isEnabled = true 8 9 } 10 } else if item == 0 { 11 leftButton.setImage(nil, for: .normal) 12 leftButton.isEnabled = false 13 }
該当する部分を変更すると、左側のボタンがクロスディゾルブで変わるようになります。
投稿2021/02/17 14:18
編集2021/02/17 15:11総合スコア5086
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/02/17 14:22
2021/02/18 06:21
2021/02/18 06:30
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。