your3i’s blog

iOSエンジニア。頑張る⚉

Viewの一つの角を角丸にする

こういうViewを作りたく

  • 高さ30の長方形
  • 左下はサイズ24の角丸

f:id:your3i:20180920214020p:plain

Try CACornerMask (Failed)

iOS11から使えるようになったCACornerMaskを使ってみる。

let view = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 30))
view.backgroundColor = .cyan
view.layer.cornerRadius = 24
view.layer.maskedCorners = [.layerMinXMaxYCorner]
PlaygroundPage.current.liveView = view


こんな感じになった、ダメそう
f:id:your3i:20180920215259p:plain

Try UIBezierPath Part1(Failed)

CACornerMaskはどうせiOS11以上しか使えないから、あっさり諦めた。
次はUIBezierPathのいつものやり方でやってみる。

let view = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 30))
view.backgroundColor = .cyan
let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: .bottomLeft, cornerRadii: CGSize(width: 24, height: 24))
let mask = CAShapeLayer()
mask.path = path.cgPath
view.layer.mask = mask
PlaygroundPage.current.liveView = view

今回はこんな感じ。良さそうでは?でもよくみたら、角丸のサイズは24じゃなさそう。
f:id:your3i:20180920220518p:plain

init(roundedRect:byRoundingCorners:cornerRadii:)のcornerRadiiを調べてみたら。どうやら長方形の幅か高さかの半分を超えた値を設定すると、自動的に半分にしてもらうらしい。要するに、高さ30の長方形だから、15の角丸にされた。

The radius of each corner oval. Values larger than half the rectangle’s width or height are clamped appropriately to half the width or height.

半分を超えたサイズの角丸はもう角丸じゃなくなったかな。

Try UIBezierPath Part2(Succeeded)

描くしかない…でも角丸はどうやって描くんだ😅

A -> B -> C -> ... -> E -> A 多分こう。
f:id:your3i:20180920222049p:plain

let view = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 30))
view.backgroundColor = .cyan

// pathを描く
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: view.bounds.height - 24))
path.addLine(to: .zero)
path.addLine(to: CGPoint(x: view.bounds.width, y: 0))
path.addLine(to: CGPoint(x: view.bounds.width, y: view.bounds.height))
path.addLine(to: CGPoint(x: 24, y: view.bounds.height))
path.addArc(withCenter: CGPoint(x: 24, y: view.bounds.height - 24), radius: 24, startAngle: CGFloat.pi / 2, endAngle: -(CGFloat.pi / 4), clockwise: true)

let mask = CAShapeLayer()
mask.path = path.cgPath
view.layer.mask = mask
PlaygroundPage.current.liveView = view

いい感じになった。YEAH~
f:id:your3i:20180920222558p:plain

そのあとハマったところ

viewのサイズがruntimeで変わることがある場合(autolayoutで)、maskのサイズは自動的に変わらないから、viewのサイズが変わる度にmaskのサイズを指定し直す必要がある。

viewのサイズ変わること、外から検知できなさそうだから、subclass作るしかなさそう。

最終的に

class LeftBottomRoundCornerView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupMask()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupMask()
    }
    override var bounds: CGRect {
        didSet {
            setupMask()
        }
    }
    private func setupMask() {
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0, y: bounds.height - 24))
        path.addLine(to: .zero)
        path.addLine(to: CGPoint(x: bounds.width, y: 0))
        path.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
        path.addLine(to: CGPoint(x: 24, y: bounds.height))
        path.addArc(withCenter: CGPoint(x: 24, y: bounds.height - 24), radius: 24, startAngle: CGFloat.pi / 2, endAngle: -(CGFloat.pi / 4), clockwise: true)
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        layer.mask = mask
    }
}