CS193p 2021 #6 Protocols Shapes
2 min readJun 3, 2021
影片
課程範例
Pie.swift
import SwiftUIstruct Pie: Shape {
var startAngle: Angle
var endAngel: Angle
var clockwise = false
// https://zh.wikipedia.org/zh-tw/三角函数
func path(in rect: CGRect) -> Path {let center = CGPoint(x: rect.midX, y: rect.midY)
print("path", rect, center)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 path = Path()
path.move(to: center)
path.addLine(to: start)
path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngel, clockwise: !clockwise)
path.addLine(to: center)
return path
}
}
AspectVGrid.swift
import SwiftUIstruct 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 = widthThatFits(itemCount: items.count, in: geometry.size, itemAspectRatio: aspectRatio)
LazyVGrid(columns: [adaptiveGridItem(width: width)], content: {
ForEach(items) { item in
content(item).aspectRatio(aspectRatio, contentMode: .fit)
}
})
Spacer(minLength: 0)
}
}
}
private func adaptiveGridItem(width: CGFloat) -> GridItem {
GridItem(.adaptive(minimum: width), spacing: 0)
}
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
//rowCount = (itemCount - 1) / columnCount + 1
} while columnCount < itemCount
if columnCount > itemCount {
columnCount = itemCount
}
return floor(size.width / CGFloat(columnCount))
}
}
EmojiMemoryGameView.swift
import SwiftUIstruct EmojiMemoryGameView: View {
@ObservedObject var game: EmojiMemoryGame
var body: some View {
AspectVGrid(items: game.cards, aspectRatio: 2/3) { card in
if card.isMatched, !card.isFaceUp {
Rectangle()
.opacity(0)
} else {
CardView(card: card)
.padding(4)
.onTapGesture {
game.choose(card)
}
}
}
.foregroundColor(.red)
.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: .degrees(0-90), endAngel: .degrees(110-90))
.padding(5)
.opacity(0.5)
Text(card.content)
.font(font(in: geometry.size))
} else {
shape
.fill()
}
}
}
}
private func font(in size: CGSize) -> 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)
.preferredColorScheme(.dark)
}
}
MemoryGame.swift,EmojiMemoryGame.swift,MemorizeApp.swift
參考 #5