일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- flutter
- 코딩테스트
- colorofdays
- stanford
- 스터디
- UIKit
- xcode
- xml
- Masil
- WidgetTree
- UserDefault
- process
- GIT
- MVVM
- flutter #state # stateful #stateless
- 프로젝트회고
- 프로그래머스
- 알고리즘
- 오늘의 색상
- 백준
- CS193p
- ImageSlider
- Swift
- 새싹후기
- 청년취업사관학교후기
- 스위프트
- 조건문
- IOS
- collectionView
- SwiftUI
- Today
- Total
개발을 시작하는 이야기
[SwiftUI] Lecture 6: Protocols Shapes 본문
강의 보기 : Youtube :: Stanford
Protocol
특정 기능을 수행에 필요한 필수적인 속성이나 메서드를 정의한다.
protocol Moveable {
func move(by: Int)
var hasMoved: Bool { get }
var distanceFromStart: Int { get set }
}
struct PortableThing: Moveable {
func move(by: Int) {
print("\(by)만큼 움직입니다")
}
var hasMoved
var distanceFromStart
}
프로토콜은 또 다른 프로토콜을 따를 수 있다.
protocol Moveable {
var hasMoved: Bool { get }
}
protocol Messagable: Moveable {
//...
}
프로토콜에서도 제너릭을 쓸 수 있다.
대신 프로토콜에서 제너릭을 사용하기 위해서는 < >이 아닌 associated type 키워드를 통해 선언한다.
protocol Identifiable {
associatedtype ID where ID: Hashable
var id: ID { get }
}
//Or moew simply
protocol Identifiable {
associatedtype ID: Hashable
var id: ID { get }
}
@ViewBuilder
View를 리턴하는 함수를 @ViewBuilder로 만들어준다.
init(items: [Item], aspectRatio: CGFloat, @ViewBuilder content: @escaping (Item) -> ItemView) {
...
}
@ViewBuilder
func someViewBuider() -> some View { }
@escaping
커스텀 init을 통해 인자로 함수를 받을 때, 인자는 기본적으로 value type이므로 해당 함수가 init 밖에서 쓰인다면 메모리 할당을 통해 힙에 저장해야 하기 때문에 escaping으로 표기해줘야 한다.
struct AspectVGrid<Item, ItemView>: View where ItemView: View, Item: Identifiable{
...
let content: (Item) -> ItemView
init(items: [Item], aspectRatio: CGFloat, @ViewBuilder content: @escaping (Item) -> ItemView) {
//여기서 content를 @escaping으로 선언함
...
self.content = content
}
var body: some View {
GeometryReader { geometry in
VStack {
...
LazyVGrid(columns: [adaptiveGridItem(width: width)], spacing: 0) {
ForEach(items) { item in
content(item).aspectRatio(aspectRatio, contentMode: .fit)// 여기서 content 클로저가 사용됨
}
}
...
}
}
}
}
Shape
Path()는 이차원 그림을 그리는 데 필요한 다양한 함수를 갖고 있다.
func path(in rect: CGRect) -> Path {
return a Path
}
원을 그릴때 주의점은 0도가 시계의 12시 방향이 아닌 3시 방향을 기준으로 시작하게 된다.(x축이 기준이 되기 때문인 듯) 그래서 12시 방향을 기준으로 할 때는 -90을 해주면 된다.
Pie(startAngle: Angle(degrees: 0 - 90), endAngle: Angle(degrees: 110 - 90))
Pie
import SwiftUI
struct Pie: Shape {
var startAngle: Angle
var endAngle: Angle
var clockwise: Bool = false
func path(in rect: CGRect) -> Path {
let center = CGPoint(x: rect.midX, y: rect.midY)
let radius = min(rect.width, rect.height) / 2
let start = CGPoint(
x: center.x + radius * CGFloat(cos(startAngle.radians)),
y: center.y + radius * CGFloat(sin(startAngle.radians))
)
var p = Path()
p.move(to: center)
p.addLine(to: start)
p.addArc(
center: center,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: !clockwise
)
p.addLine(to: center)
return p
}
}
Path() 에서 오랜만에 사용하는 sin과 cos
View
import SwiftUI
struct EmojiMemoryGameView: View {
@ObservedObject var game: EmojiMemoryGame
var body: some View {
AspectVGrid(items: game.cards, aspectRatio: 2/3, content: { card in
if card.isMatched && !card.isFaceUp {
Rectangle().opacity(0)
} else {
CardView(card: card)
.padding(4)
.onTapGesture {
game.choose(card)
}
}
})
.foregroundColor(/*@START_MENU_TOKEN@*/.red/*@END_MENU_TOKEN@*/)
.padding(.horizontal)
}
}
struct CardView: View {
let card: EmojiMemoryGame.Card
var body: some View {
GeometryReader { geometry in
ZStack {
let shape = RoundedRectangle(cornerRadius: DrawingConstants.cornerRadius)
if card.isFaceUp {
shape.fill().foregroundColor(.white)
shape.strokeBorder(lineWidth: DrawingConstants.lineWidth)
Pie(startAngle: Angle(degrees: 0-90), endAngle: Angle(degrees: 110-90))
.padding(5)
.opacity(0.5)
Text(card.content)
.font(font(in: geometry.size))
} else if card.isMatched {
shape.opacity(0)
} else {
shape.fill()
}
}
}
}
private func font(in size: CGSize) -> Font {
Font.system(size: min(size.width, size.height) * DrawingConstants.fontScale )
}
private struct DrawingConstants {
static let cornerRadius: CGFloat = 10
static let lineWidth: CGFloat = 3
static let fontScale: CGFloat = 0.7
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let game = EmojiMemoryGame()
game.choose(game.cards.first!)
return EmojiMemoryGameView(game: game)
}
}
body의 ScrollView, LazyVGrid를 struct AsectVGrid로 상속해서 사용했다.
Preview에서 기존에 일반 모드와 다크모드를 사용하던것을 첫 카드만 뒤집힌 일반View로 변경해서 사용한다.
import SwiftUI
struct AspectVGrid<Item, ItemView>: View where ItemView: View, Item: Identifiable {
var items: [Item]
var aspectRatio: CGFloat
var content: (Item) -> ItemView
init(items: [Item], aspectRatio: CGFloat, @ViewBuilder content: @escaping (Item) -> ItemView) {
self.items = items
self.aspectRatio = aspectRatio
self.content = content
}
var body: some View {
GeometryReader { geometry in
VStack {
let width: CGFloat = widthThatFits(itemCount: items.count, in: geometry.size, itemAspectRatio: aspectRatio)
LazyVGrid(columns: [adaptiveGridItem(width: width)], spacing: 0) {
ForEach(items) { item in
content(item).aspectRatio(aspectRatio, contentMode: .fit)
}
}
Spacer(minLength: 0)
}
}
}
private func adaptiveGridItem(width: CGFloat) -> GridItem {
var gridItem = GridItem(.adaptive(minimum: width))
gridItem.spacing = 0
return gridItem
}
private func widthThatFits(itemCount: Int, in size: CGSize, itemAspectRatio: CGFloat) -> CGFloat {
var columnCount = 1
var rowCount = itemCount
repeat {
let itemWidth = size.width / CGFloat(columnCount)
let itemHeight = itemWidth / itemAspectRatio
if CGFloat(rowCount) * itemHeight < size.height {
break
}
columnCount += 1
rowCount = (itemCount + (columnCount - 1)) / columnCount
} while columnCount < itemCount
if columnCount > itemCount {
columnCount = itemCount
}
return floor(size.width / CGFloat(columnCount))
}
}
struct를 사용하여 상속하는건 예제로 보고 있으면 이해도 되고, 왜 이렇게 하는게 좋은가는 알겠지만, 실제로 내가 적용하려면 아직은 기능을 십분 활용을 못한채 제한적으로 밖에 사용하지 못한다. 더 많이 사용해보고 익숙해져야겠다.
'개발 이야기 > Swift' 카테고리의 다른 글
[SwiftUI] Lecture 8: Animation Demonstration (0) | 2022.04.21 |
---|---|
[SwuftUI] Lecture 7: ViewModifier Animation (0) | 2022.04.19 |
접근제어는 왜 사용해야 할까? (0) | 2022.04.15 |
[SwiftUI] Lecture 5: Properties Layout @ViewBuilder (0) | 2022.04.14 |
[SwiftUI] Lecture 4: Memorize Game Logic (0) | 2022.04.14 |