🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1768閲覧

UITabBarをエリア分け、スクロールしたい(xcode)

onigiriZ

総合スコア3

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2021/02/17 01:53

編集2021/02/17 01:54

やりたいこと

swiftでアプリ開発をしています。その中で、UITabBarをカスタマイズしたいと考えています。

実現したいタブバーの動画↓
![イメージ説明

まず、タブバーアイテムがスクロールしています。
そして、薄い縦線のようなもので区切られた一番右のタブバーアイテムは、
そのスクロールに干渉されていないような作りになっています。
また、分かりづらいですが、一番左のタブバーアイテムはある閾値を超えるとスクロールについていくようになっています。

1 TabBarの中で薄い縦線のようなものでエリアを区切る方法
2 TabBarItemを横スクロールする方法
を探しています。

実装方法をご存じの方いましたら、教えていただきたいです。
よろしくお願いします。

開発環境

Xcode 12.4
Mac 10.15.7
iOS 14.4

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TsukubaDepot

2021/02/17 14:20

hoshi-takanoriさん すいません、また行き違いになってしまいました。 質問者さんは tabBar ではなくて、スクロール可能なメニューバーを期待されているような気がしました。
guest

回答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
TsukubaDepot

総合スコア5086

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TsukubaDepot

2021/02/17 14:22

ちなみに、本質的なところは UICollectionView の部分となります。 コードは長いですが、 CollectionView、とくに dataSource や delegate だけを追っていけば理解しやすいかと思います。 ただ、スクロールの判定には親クラスである UIScrollView の delegate を使う必要があります。
onigiriZ

2021/02/18 06:21

コメントありがとうございます。 uitabbarではなくcollectionviewを使う発想がありませんでした。 そして、実装コードまで載せてくださり、感謝しかありません。 実装してみたいと思います。
TsukubaDepot

2021/02/18 06:30

UITabBarは画面下部に配置し、上部に配置した View Controller を切り替える目的で使います。 OS標準の部品で、その設置場所はガイドラインで割合厳しく指摘されているようですので、配置を変えると審査落ちする可能性が高くなると言われています(自分で使う分には問題ないと思いますが)。 GoodNotes 5 はダウンロードしていないので想像でやりましたが、おそらくツールバーに表示できないアイコンをスクロールさせて表示させているんだろうと思います。 一番左に表示させるアイコンの表示基準はわかりませんので、今回は奇数番目のアイテムだけ表示という方法でやりましたが、特定のアイコンだけ表示させるのは、判定基準を変えるだけなので、おそらくそれほど難しい話ではないと思います。 左に配置されたボタンはもちろん、CollectionView 上のアイコンもタップされたときの delegte を呼び出せば判定できますので、それほど難しい話ではないと思っています。 また、なるべく確実に再現できるよう、今回は全てコードベースでやってしまいましたが、もちろん StoryBoard で配置し制約をつけたり、あるいは配置と制約をコードベースでやっても問題ありません。 カスタムセルも xib は使わずにやってしまいましたが、xib などを使った方が楽な場合もあります。 一番厄介なのは、CollectionView に表示させるアイコンの数を規定する(padding や spacing を設定する)ことかもしれません。 いずれにしても、このコードをもとにご自由は発想で発展させていただければそれに越したことはありませんので、どんどん拡張していただければと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問