I am following Stanfords' CS193p Developing Apps for iOS online course. I am using Xcode 11.5. (I didn't update because that's the version the course instructor (Paul Heagarty) is using.)
I'm trying to do the Assignment 3 (Set Game). Currently (to avoid duplicating code) I am trying to replace this fragment:
VStack {
ForEach(0..<numberOfShapes) { index in
if self.card.shape == .diamond {
ZStack {
Diamond().fill()
.opacity(self.opacity)
Diamond().stroke(lineWidth: self.shapeEdgeLineWidth)
}
}
if self.card.shape == .squiggle {
ZStack {
Rectangle().fill()
Rectangle().stroke(lineWidth: self.shapeEdgeLineWidth)
}
}
if self.card.shape == .oval {
ZStack {
Ellipse().fill()
Ellipse().stroke(lineWidth: self.shapeEdgeLineWidth)
}
}
}
}
With this fragment:
VStack {
ForEach(0..<numberOfShapes) { index in
ZStack {
shape(self.card.shape).opacity(self.opacity)
shape(self.card.shape).stroke(lineWidth: 2.5) // ERROR here: Value of type 'some View' has no member 'stroke'
}
}
}
And this @ViewBuilder function:
@ViewBuilder
func shape(_ shape: SetGameModel.Card.Shape) -> some View {
if shape == .diamond {
Diamond()
} else if shape == .squiggle {
Rectangle()
} else {
Ellipse()
}
}
And here is full View code:
import SwiftUI
struct SetGameView: View {
@ObservedObject var viewModel: SetGameViewModel
var body: some View {
Grid(viewModel.cards) { card in
CardView(card: card).onTapGesture {
self.viewModel.choose(card: card)
}
.padding(5)
}
.padding()
.foregroundColor(Color.orange)
}
}
struct CardView: View {
var card: SetGameModel.Card
var numberOfShapes: Int {
switch card.numberOfShapes {
case .one:
return 1
case .two:
return 2
case .three:
return 3
}
}
var opacity: Double {
switch card.shading {
case .open:
return 0.0
case .solid: // filled
return 1.0
case .striped: // you can use a semi-transparent color to represent the “striped” shading.
return 0.33
}
}
var color: Color {
switch card.color {
case .green:
return Color.green
case .purple:
return Color.purple
case .red:
return Color.red
}
}
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: cornerRadius).fill(Color.white)
RoundedRectangle(cornerRadius: cornerRadius)
.stroke(lineWidth: card.isChosen ? chosenCardEdgeLineWidth : normalCardEdgeLineWidth)
.foregroundColor(card.isChosen ? Color.red : Color.orange)
VStack {
ForEach(0..<numberOfShapes) { index in
ZStack {
shape(self.card.shape).opacity(self.opacity)
shape(self.card.shape).stroke(lineWidth: 2.5) // ERROR here: Value of type 'some View' has no member 'stroke'
}
}
}
.foregroundColor(self.color)
.padding()
}
.aspectRatio(cardAspectRatio, contentMode: .fit)
}
// MARK: - Drawing Constants
let cornerRadius: CGFloat = 10.0
let chosenCardEdgeLineWidth: CGFloat = 6
let normalCardEdgeLineWidth: CGFloat = 3
let shapeEdgeLineWidth: CGFloat = 2.5
let cardAspectRatio: CGSize = CGSize(width: 2, height: 3) // 2/3 aspectRatio
}
@ViewBuilder
func shape(_ shape: SetGameModel.Card.Shape) -> some View {
if shape == .diamond {
Diamond()
} else if shape == .squiggle {
Rectangle()
} else {
Ellipse()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
SetGameView(viewModel: SetGameViewModel())
}
}
And I have this Error:
Value of type 'some View' has no member 'stroke'
I can't figure this out, what's wrong? How can I fix it?
Please try to help me fix it in some way, that I will understand as a beginner 🙏
Diamond() is my custom Shape by the way (like Rectangle()). If you also need ViewModel, Model or some other files to help me fix it, let me know :-)
stroke
is defined on Shape
, not View
and you are returning Shape
s, not just View
s. You need to change the return type of shape
to some Shape
.
Sadly @ViewBuilder
needs the return type to be some View
, not some Shape
, so you need to remove the @ViewBuilder
attribute and make sure your function returns the same Shape
from each branch. To achieve this, you can implement a type-erased Shape
, called AnyShape
similar to AnyView
for View
and return the type erased version of each Shape
.
struct AnyShape: Shape {
init<S: Shape>(_ wrapped: S) {
_path = { rect in
let path = wrapped.path(in: rect)
return path
}
}
func path(in rect: CGRect) -> Path {
return _path(rect)
}
private let _path: (CGRect) -> Path
}
func shape(_ shape: SetGameModel.Card.Shape) -> some Shape {
if shape == .diamond {
return AnyShape(Diamond())
} else if shape == .squiggle {
return AnyShape(Rectangle())
} else {
return AnyShape(Ellipse())
}
}