#はじめに
付箋のView生成部分のコードがわかりずらかったので全面的に書き直しました。(7/Nov/2019)
前回のPreferenceKeyの記事が重かったので、今回は軽めの内容です。
SwiftUIには色々なModifier
が準備されています。下記の例ですとfont
, lineLimit
, frame
, foregroundColor
がそれにあたります。簡単にUIの変更が出来てとても便利ですね。
Text("Hello World")
.font(.headline)
.lineLimit(5)
.frame(width: UIScreen.main.bounds.width/2)
.foregroundColor(.white)
#今回作るもの
SwiftUIではさらにViewModifier
を利用してカスタムのModifierを作ることが出来ます。
今回は下記のようにText
を付箋のように表示するModifier
を作ってみたいと思います。
Text("If today were the last day of my life, would I want to do what I am about to do today")
.StickyNote( color: .green, ceaseSize: 50, cornerRadius: 20, shadowLength: 10))
#ViewModifier
ViewModifier
プロトコルを継承してカスタマイズViewModifierを作ります。そのためにはfunc body(content: Self.Content) -> Self.Body
を実装しなければなりません。
content
はモディファイする対象のViewへのProxyになります。前述の例でいうとText("If today were the last day of my life, would I want to do what I am about to do today")
になります。下の例では対象のViewのbackgroundとしてStickyNote
という名のViewが表示されますので、今回の付箋の例ではStickyNote
のViewで付箋の画像を生成すれば良いという訳です。非常にわかりやすく、簡単です。
ひとつ注意点としてはbackground
を設定する前にpadding
を使って付箋のサイズを前もって設定しなければならない点です。なぜならStickyNote
側のGeometryReader
でcontent
のサイズ(付箋の矩形のサイズ)を取得する必要があるからです。
func body(content:Content) -> some View {
ZStack(content: {
content
.padding(.horizontal, 30) //左右の余白
.padding(.vertical, 40) //上下の余白
.background(StickyNote()) //対象のView(Text)の背景として付箋のViewを設定する
.shadow(radius: shadowLength) //影
})
}
#付箋描写部分の実装
次に付箋の画像生成部分です。GeometryReader
を使って対象Viewのサイズを取得しPath
を使って画像を生成して返しています。GeometryReaderに関しては過去記事(チュートリアルから一歩踏み出したSwiftUIのCustom Viewの作り方ーその1(GeometryReader編))を参考にしてください。
func StickyNote() -> some View {
return GeometryReader { geometry in
ZStack {
//付箋の部分
Path { path in
let w = geometry.size.width
let h = geometry.size.height
let d = min(w/self.divide, h/self.divide)
let m = min(d, self.ceaseSize)
let r = min(self.cornerRadius, m)
path.move(to: CGPoint(x: 0, y: r))
path.addLine(to: CGPoint(x: 0, y: h-r))
path.addArc(center: CGPoint(x: r, y: h-r), radius: r, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 90), clockwise: true)
path.addLine(to: CGPoint(x: r, y: h))
path.addLine(to: CGPoint(x: w-m, y: h))
path.addLine(to: CGPoint(x: w, y: h-m))
path.addLine(to: CGPoint(x: w, y: r))
path.addArc(center: CGPoint(x: w-r, y: r), radius: r, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 270), clockwise: true)
path.addLine(to: CGPoint(x: r, y: 0))
path.addArc(center: CGPoint(x: r, y: r), radius: r, startAngle: Angle(degrees: 270), endAngle: Angle(degrees: 180), clockwise: true)
}
.fill(self.color)
//付箋の折り返し部分
Path { path in
let w = geometry.size.width
let h = geometry.size.height
let d = min(w/self.divide, h/self.divide)
let m = min(d, self.ceaseSize)
let r = min(self.cornerRadius, m)
path.move(to: CGPoint(x: w-m, y: h))
path.addLine(to: CGPoint(x: w-m, y: h-m+r))
path.addArc(center: CGPoint(x: w-m+r, y: h-m+r), radius: r, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)
path.addLine(to: CGPoint(x: w, y: h-m))
path.addLine(to: CGPoint(x: w-m, y: h))
}
.fill(Color.black).opacity(0.4)
}
}
}
}
#可読性の向上
最後にModifier
をViewのメソッドにして可読性を高めます。
これにより.modifier(StickyNoteModifier()
ではなく.StickyNote()
で使えるようになります。
extension View {
func stickyNote(color: Color, ceaseSize: CGFloat, cornerRadius: CGFloat, shadowLength: CGFloat)->some View {
self.modifier(StickyNoteModifier(color: color, ceaseSize: ceaseSize, cornerRadius: cornerRadius, shadowLength: shadowLength))
}
}
#まとめ
Modifier
を使うと、非プログラマーが関与する必要がない部分を隠蔽し、デザイナーなど非プログラマーがLiveViewを見ながらUIを細かに調整することを、簡単なパラメーターの変更のみで可能にします。
今回の例ですと、色、コーナーの大きさ、付箋の折り返し部分の大きさなどを調整することができます。もちろん文字長が変わっても、複数行になっても自動で調整されます。
Text("If today were the last day of my life, would I want to do what I am about to do today")
.StickyNote( color: .green, ceaseSize: 50, cornerRadius: 20, shadowLength: 10))
-
color
- 付箋の色
-
ceaseSize
- 付箋の折り返し部分の大きさ
-
cornerRadius
- 角丸の大きさ
-
shadowLength
- 影の大きさ(0で影なし)
#コード全文
import SwiftUI
struct StickyNoteView: View {
var body: some View {
Text("If today were the last day of my life, would I want to do what I am about to do today")
.font(.headline)
.lineLimit(5)
.frame(width: UIScreen.main.bounds.width/2)
.stickyNote(color: .green, ceaseSize: 50, cornerRadius: 20, shadowLength: 10)
}
}
extension View {
func stickyNote(color: Color, ceaseSize: CGFloat, cornerRadius: CGFloat, shadowLength: CGFloat)->some View {
self.modifier(StickyNoteModifier(color: color, ceaseSize: ceaseSize, cornerRadius: cornerRadius, shadowLength: shadowLength))
}
}
struct StickyNoteModifier: ViewModifier {
var color: Color = .green
var ceaseSize: CGFloat = 50.0
var cornerRadius:CGFloat = 20.0
var shadowLength: CGFloat = 10.0
private let divide: CGFloat = 2
private let paddingHorizontal: CGFloat = 30.0
private let paddingVertical: CGFloat = 40.0
func body(content:Content) -> some View {
ZStack(content: {
content
.padding(.horizontal, 30)
.padding(.vertical, 40)
.background(StickyNote())
.shadow(radius: shadowLength)
})
}
// StickyNote drawing
func StickyNote() -> some View {
return GeometryReader { geometry in
ZStack {
Path { path in
let w = geometry.size.width
let h = geometry.size.height
let d = min(w/self.divide, h/self.divide)
let m = min(d, self.ceaseSize)
let r = min(self.cornerRadius, m)
path.move(to: CGPoint(x: 0, y: r))
path.addLine(to: CGPoint(x: 0, y: h-r))
path.addArc(center: CGPoint(x: r, y: h-r), radius: r, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 90), clockwise: true)
path.addLine(to: CGPoint(x: r, y: h))
path.addLine(to: CGPoint(x: w-m, y: h))
path.addLine(to: CGPoint(x: w, y: h-m))
path.addLine(to: CGPoint(x: w, y: r))
path.addArc(center: CGPoint(x: w-r, y: r), radius: r, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 270), clockwise: true)
path.addLine(to: CGPoint(x: r, y: 0))
path.addArc(center: CGPoint(x: r, y: r), radius: r, startAngle: Angle(degrees: 270), endAngle: Angle(degrees: 180), clockwise: true)
}
.fill(self.color)
Path { path in
let w = geometry.size.width
let h = geometry.size.height
let d = min(w/self.divide, h/self.divide)
let m = min(d, self.ceaseSize)
let r = min(self.cornerRadius, m)
path.move(to: CGPoint(x: w-m, y: h))
path.addLine(to: CGPoint(x: w-m, y: h-m+r))
path.addArc(center: CGPoint(x: w-m+r, y: h-m+r), radius: r, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)
path.addLine(to: CGPoint(x: w, y: h-m))
path.addLine(to: CGPoint(x: w-m, y: h))
}
.fill(Color.black).opacity(0.4)
}
}
}
}
struct StickyNoteView_Previews: PreviewProvider {
static var previews: some View {
StickyNoteView()
}
}