録音/録画ボタンの作り方〜角の丸いビュー
ここでは iOS や iPadOS でよく使われている、赤い丸の録音・録画ボタンの作り方を説明します。
なお、録音や録画そのものの方法については、この記事では扱いません。
1. ここで作るボタンについて
iOS/iPadOS ではカメラやボイスメモなど、録音または録画を行うためのボタンは次のような赤い丸ボタンが使われています。
そして、それをタップして録音(または録画)が始まると、四角い赤いボタンに切り替わります。
多くの人が慣れているユーザーインターフェイスで直感的にわかりやすいですね。
ここでは、この丸いボタンの作り方、丸いボタンから四角いボタンへのアニメーション方法を説明します。 さらについでに、ボイスメモの UI では四角いビューのうちで上側の2つの角だけ丸くなっています。
このように、丸くする角の部位を指定する方法についても説明します。
2. 録画ボタンの作り方
さて、最終的にはこんなボタンにしたいと思っています。
でも、こんな程度のボタンでも、意外といろんな物が混ざって出来ていますから、もっと単純な例からはじめて順番にひとつひとつバラバラにして考えていきます。
まずは、赤い丸ボタンの作り方からはじめましょう。
2-1. 丸いボタンの作り方
まずはオブジェクトライブラリからストーリーボードにボタン (Button) をひとつ配置します。
オブジェクトライブラリを開くボタンは Xcode 11 から、プラスアイコンに変更されています。キーボードショートカットは ⇧ Shift + ⌘ command + L です。
ボタンは適当な場所に配置して、サイズを幅 50、高さ 50 位にしておきます。
ボタンのラベルの文字をクリアします。
背景色 Background は System Red Color を選択します。
ここまでの設定で、次のように単純な四角の赤いボタンになるはずです。
次に Xcode のアシスタントエディタを開いて、ボタンのアウトレットとアクションを作成します。
Xcode の使い方に慣れていない方は、次のビデオを参考にしてください。左側のウィンドウからアシスタントエディタ内にコネクションを設定する場合は、 右ボタンでドラッグします。
import UIKit class ViewController: UIViewController { @IBOutlet weak var recordButton: UIButton! override func viewDidLoad() { super.viewDidLoad() } @IBAction func recordButtonTapped(_ sender: Any) { } }
これで準備は完了です。この赤い四角ボタンを丸ボタンにしましょう。
ボタンの場所を (x,y) 座標で (75, 75)、サイズを幅 (width) 50、高さ (height) 50 とするために、 ボタン (recordButton) の frame プロパティに次の値をセットします。
recordButton.frame = CGRect(x:75, y:75, width:50, height:50)
正方形のボタンの一辺の半分の長さを角の半径とすることで、丸ボタンにします。上で幅と高さを 50 にしたので、 角の半径は 25 にします。このためにボタンの layer の cornerRadiusプロパティを 25 にします。
recordButton.layer.cornerRadius = 25.0 recordButton.layer.masksToBounds = true
layer 属性はそれぞれのビューが持っている CALayer (Core Animation レイヤー) ですが、 今回のように境界線を変更したときに、その境界線でレイヤーをクリップするには masksToBounds プロパティを true にします。
上記コードは viewDidAppear に書き、次のようになります。
override func viewDidLoad() { super.viewDidLoad() } override func viewDidAppear(_ animated: Bool) { recordButton.frame = CGRect(x:75,y:75,width:50,height:50) recordButton.layer.masksToBounds = true recordButton.layer.cornerRadius = 25.0 }
この結果、次のようになります。
ちなみに、ストーリーボード上では見た目は変わりません。シミュレータか実機で実行して確認してください。
コードではなく、ストリーボードからも設定可能
ここでは、あとでプログラムから各種プロパティを変更するのと、 プロパティ間に関連性がある (例えば角の半径は一辺の長さの半分など) のでコードでプロパティを変更するようにしました。
しかし、動的に操作する必要のない場合には、ストーリーボードから layer プロパティを設定することも可能です。
なお、今回のコードはデバイスのオリエンテーション (縦横) の変更に対応していないので、Info.plist の Supported interface orientations で Portrait (bottom home button) のみを設定してください。
2-2. 丸ボタンから四角ボタンへのアニメーション
UI コンポーネントの基底クラスである UIView には、アニメーション機能が実装されています。 UIView のスタティックメソッド animate メソッドを次の形で呼び出すことで、現在の状態から遷移先の状態に徐々に移り変わります。
UIView.animate(withDuration: /*変化に必要な時間(秒)*/) { //遷移する先の状態 }
丸ボタンの時にタップすると少し小さな四角ボタンに移り変わり、四角ボタンの状態の時にタップすると丸ボタンに戻るようにするには、 現在の形状を保持するフラグを1つ用意して、次のように書きます。
import UIKit class ViewController: UIViewController { var isRecording = false @IBOutlet weak var recordButton: UIButton! override func viewDidLoad() { super.viewDidLoad() } override func viewDidAppear(_ animated: Bool) { recordButton.frame = CGRect(x:75,y:75,width:50,height:50) recordButton.layer.masksToBounds = true recordButton.layer.cornerRadius = 25 } @IBAction func recordButtonTapped(_ sender: Any) { if isRecording { UIView.animate(withDuration: 0.2) { self.recordButton.frame = CGRect(x:75,y:75,width:50,height:50) self.recordButton.layer.cornerRadius = 25 } } else { UIView.animate(withDuration: 0.2) { self.recordButton.frame = CGRect(x:75,y:75,width:30,height:30) self.recordButton.layer.cornerRadius = 3.0 } } isRecording = !isRecording } }
これを実行すると、次のようになります。
確かに丸ボタンから四角ボタン、四角ボタンから丸ボタンに移り変わっていることが確認できますね。
ただ、これだけですと、サイズが小さくなるに伴って、ボタンが左上側に動いてしまっていますので、少しだけ frame の座標を補正する必要があります。
図形の中心を変えないようにするには、下の図からわかるように四角ボタンは 10px だけ右下にずらせば良いです。
そこで、四角いボタンの時の座標、x と y に 10 足します。
... } else { UIView.animate(withDuration: 0.2) { //次の x, y 座標を変更 self.recordButton.frame = CGRect(x:75+10,y:75+10,width:30,height:30) self.recordButton.layer.cornerRadius = 3.0 } }
再度動作確認すると、次のようになります。
確かにその場にとどまったままで、形が変わっています。
2-3. 画面下に移動して黒っぽいバーの中央に配置
さて、丸ボタンから四角ボタンへのアニメーションの仕組みができたところで、場所を移動しましょう。
もともとの目標は画面の下側に次のようなボタンを作ることでした。
そこで、画面下側にオブジェクトライブラリから新たに UI View を配置します。 背景色は黒っぽく設定します (ここでは Custom Color から Lead を選びました)。
Constraint は左右端から 0、下端から 0 とし、高さ 100 とします。
そして、この View (名前を Base View とします) の参照として baseView という名前でアウトレットを作成しておきます。
@IBOutlet weak var baseView: UIView!
次に上で作成した赤いボタンを、この Base View のサブビューとしてぶら下げます。
これによって、赤いボタンの x , y 座標は Base View の左上端からの相対座標で設定できるようになります。
このように準備した上で、上記のコードを一旦整理して、次のようにしておきます。
import UIKit class ViewController: UIViewController { var isRecording = false var w: CGFloat = 0 var h: CGFloat = 0 let d: CGFloat = 50 let l: CGFloat = 28 @IBOutlet weak var recordButton: UIButton! @IBOutlet weak var baseView: UIView! override func viewDidLoad() { super.viewDidLoad() } override func viewDidAppear(_ animated: Bool) { w = baseView.frame.size.width h = baseView.frame.size.height initRoundCorners() showStartButton() } @IBAction func recordButtonTapped(_ sender: Any) { if isRecording { UIView.animate(withDuration: 0.2) { self.showStartButton() } } else { UIView.animate(withDuration: 0.2) { self.showStopButton() } } isRecording = !isRecording } func initRoundCorners(){ recordButton.layer.masksToBounds = true } func showStartButton() { recordButton.frame = CGRect(x:(w-d)/2,y:(h-d)/2,width:d,height:d) recordButton.layer.cornerRadius = d/2 } func showStopButton() { recordButton.frame = CGRect(x:(w-l)/2,y:(h-l)/2,width:l,height:l) recordButton.layer.cornerRadius = 3.0 } }
だいぶ変わったように見えるかもしれませんが、定数をコードの上でまとめて書いて、 その他なるべくハードコードを減らすようにしただけです。考え方は変わりません。
この結果、次の画面のように画面下側の黒っぽいビューの中でボタンが、丸から四角にアニメーションします。
だいぶ、完成に近づきました。
2-4. 二重丸ボタンの仕組み
二重丸ボタンにするために、赤いボタンの外側に白い枠線を描きます。
白い枠線を描くためにUIView をさらに2つ配置しました。1つはサイズ縦横 62 の白色の View。これを outerCircle という名前で参照します (アウトレットを作ります)。 そしてもう1つは縦横 58 で Base View と同じ色の黒っぽい View。これを innerCircle という名前で参照します。
それぞれ、半径31、29 の円形のビューにして、赤いボタンの下側に配置します。これによって、少し大きい白色の枠線が見え、次に黒っぽい色、そして赤いボタンという風に重なります。
initRoundCorners 関数内で、マスク、色、角の半径を指定します。これらは動かないので、サイズと位置はストーリーボードの Constraint で Base View の中央に静的に配置しています。
@IBOutlet weak var outerCircle: UIView! @IBOutlet weak var innerCircle: UIView! ... func initRoundCorners(){ recordButton.layer.masksToBounds = true outerCircle.layer.masksToBounds = true outerCircle.layer.cornerRadius = 31 outerCircle.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) innerCircle.layer.masksToBounds = true innerCircle.layer.cornerRadius = 29 innerCircle.backgroundColor = #colorLiteral(red: 0.1298420429, green: 0.1298461258, blue: 0.1298439503, alpha: 1) }
以上で、次のような動作が実現できました。
このような赤い丸いボタンがあれば、直感的に「何か録音でもしそうだな」とわかりますし、 四角い赤いボタンになれば「もう一度押せば停止するだろう」とわかります。UI の大事さが改めて感じられますね。
3. 部分的に角を丸くする方法
最後に Base View の左上の角と、右上の角を丸くしましょう。実際に iOS 付属の Voice Memo でも同じように丸みがついています。
ここまで角を丸くして、四角いビューを丸いビューとして利用する例をみてきました。そのときは layer プロパティの cornerRadius プロパティを設定しました。
部分的に丸みをつける角を指定するには、cornerRadius に加え、 maskedCorners プロパティを設定します。
baseView.layer.masksToBounds = true baseView.layer.cornerRadius = 10 baseView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
以上で、最終的に次のようなビューが作成できました。
4. まとめ
おさらいすると、ポイントは次のようになります。
- UIView の layer プロパティで cornerRadius = 半径 と masksToBounds = true を設定することで円形のビューが作れる。(ストーリーボードでも静的に設定可能)
- 丸いボタンと四角いボタンの移り変わりは、同じボタンの角の半径とサイズをアニメーションで切り替えることで実現できる。
- 二重丸はサイズ違いのビューを重ねて作っている。(今回は外側の白い部分は縦横 62、内側の黒い部分の縦横 58、赤いボタン縦横 50)
- ビューの角の一部だけ丸くするには maskedCorners で丸くする角を指定。
もう一度コード全体を書いておきます。うまく動かない時に参考にしてください。
ストーリーボードは次のようになりました。Base View の下のビューの設定順番は大事です。
import UIKit class ViewController: UIViewController { var isRecording = false var w: CGFloat = 0 var h: CGFloat = 0 let d: CGFloat = 50 let l: CGFloat = 28 @IBOutlet weak var recordButton: UIButton! @IBOutlet weak var baseView: UIView! @IBOutlet weak var outerCircle: UIView! @IBOutlet weak var innerCircle: UIView! override func viewDidLoad() { super.viewDidLoad() } override func viewDidAppear(_ animated: Bool) { w = baseView.frame.size.width h = baseView.frame.size.height initRoundCorners() showStartButton() } @IBAction func recordButtonTapped(_ sender: Any) { if isRecording { UIView.animate(withDuration: 0.2) { self.showStartButton() } } else { UIView.animate(withDuration: 0.2) { self.showStopButton() } } isRecording = !isRecording } func initRoundCorners(){ recordButton.layer.masksToBounds = true baseView.layer.masksToBounds = true baseView.layer.cornerRadius = 10 baseView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] outerCircle.layer.masksToBounds = true outerCircle.layer.cornerRadius = 31 outerCircle.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) innerCircle.layer.masksToBounds = true innerCircle.layer.cornerRadius = 29 innerCircle.backgroundColor = #colorLiteral(red: 0.1298420429, green: 0.1298461258, blue: 0.1298439503, alpha: 1) } func showStartButton() { recordButton.frame = CGRect(x:(w-d)/2,y:(h-d)/2,width:d,height:d) recordButton.layer.cornerRadius = d/2 } func showStopButton() { recordButton.frame = CGRect(x:(w-l)/2,y:(h-l)/2,width:l,height:l) recordButton.layer.cornerRadius = 3.0 } }
以上、iOS や iPadOS で録音や録画でよく使われる赤い丸ボタンの作り方を説明しました。
iOS/iPadOS では UI の重要さが初めから認識されていて、 CALayer (Core Animation レイヤー) というレイヤーが初めから全てのビューに組み込まれています。
これを最大限に利用することによって、UI の表現豊かな iPhone アプリを開発することが可能になります。