Can't use 'shape' as Type in SwiftUI
You need to use one type for shape in your model, like
struct SetCard: Identifiable {
var id: Int
var shape: AnyShape // << here !!
var numberOfShapes: Int
var colorOfShapes: Color
// var shadingOfShapes: Double
}
The AnyShape
declaration and demo of usage can be observed in my different answer https://stackoverflow.com/a/62605936/12299030
And, of course, you have to update all other dependent parts to use it (I skipped that for simplicity).
How to pass a SwiftUI Shape as argument
Here is the required changes to make it work:
For the class declaration:
public struct Toast<Content: Shape>: View { ... }
For property declaration:
var shape: Content
For init declaration:
shape: Content
How to use same set of modifiers for various shapes
You can abstract some of the repetition away by using helper functions and extensions.
In the simplified example below I use a
@ViewBuilder
to clean up the code that we are returning. There is no need to useAnyView
and it makes the code much easier to read.It would be great if we could return
some Shape
however this is
not currently possible and results in errors. This is why the stroke
value has to be repeated for eachShape
in thegetShape
function, otherwise we could have made the extension onShape
instead ofView
.I create an extension on
View
that allows us to combine the modifiers into one function making it more readable and easier to use. Though honestly this part is optional and you could use your two modifiersframe
androtationEffect
.The
@ViewBuilder getShape(shape:index:)
returns the the shape you have picked with its chosen color, this is then used by the functioncreateShape(shape:index:)
where you add the custom modifier that we created as an extension onView
.Finally we create our shape
This should give you a starting point.
struct ShapeView: View {
@ViewBuilder // 1
func getShape(shape: Int, i: Int) -> some View {
switch shape {
case 0:
Rectangle().stroke(Color.red)
case 1:
Capsule().stroke(Color.red)
case 2:
Ellipse().stroke(Color.red)
default:
Rectangle().stroke(Color.red)
}
}
func createShape(shape: Int, index: Int) -> some View { // 3
getShape(shape: shape, i: index)
.myModifier(width: 200, height: 100, index: index, angleStep: 30)
}
var body: some View {
createShape(shape: 2, index: 1) // 4
}
}
// 2
extension View {
func myModifier(width: CGFloat, height: CGFloat, index: Int, angleStep: Double) -> some View {
self
.frame(width: width, height: height)
.rotationEffect(Angle(degrees: Double(index) * Double(angleStep)))
}
}
struct ShapeView_Previews: PreviewProvider {
static var previews: some View {
ShapeView()
}
}
It is such a shame that we cannot return some Shape
from the @ViewBuilder
or if there existed a @ShapeBuilder
as this would mean that we wouldn't have to add the stroke to each shape individually, as a View
cannot have a stroke.
Array of shapes - Protocol can only be used as a generic constraint
Wrap them into AnyView
type eraser, like
let diceShapes: [AnyView] = [AnyView(Triangle()), AnyView(Pentagon()), ....]
Get Shape for view dynamically in SwiftUI
By combining @Asperi's answer with
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
}
I can change it to
func getShape(suite:Suite) -> some Shape {
switch suite {
case .club:
return AnyShape(Club())
case .diamond:
return AnyShape(Diamond())
case .heart:
return AnyShape(Heart())
case .spade:
return AnyShape(Spade())
}
}
struct CardView: View {
let suit : Suite
let rank : Rank
var body: some View {
getShape(suite: suit)
.fill(Color.red)
.frame(width: 100, height: 100)
}
Question about View as Type Constraint in SwiftUI
With generics we should remember that types are inferred at compiled type, so views should be known at the moment expression written. So SwiftUI uses builder pattern for this purpose.
Here is a demo how it could be solved with support for generic page and some default one provided.
Tested with Xcode 12.4 / iOS 14.4
class Lesson<T: View>: Identifiable {
let id = UUID()
var lessonName: String
var builder: (() -> T)? // << optional builder
@ViewBuilder
func lessonPage() -> some View { // ViewBuilder gives possibility
if let page = builder?() { // to provide default view
page
} else {
DefaultLessonPageView()
}
}
init(lessonName: String,
lessonPage: (() -> T)? = nil) { // by default no view
self.lessonName = lessonName
self.builder = lessonPage
}
}
struct ButtonPageView<Page: View>: View {
var lesson: Lesson<Page>
var body: some View {
NavigationLink(destination: lesson.lessonPage()) {
Text("")
}
}
}
and now we can use those as
var body: some View {
// no view, so let compiler know
ButtonPageView<Never>(lesson: Lesson(lessonName: "First"))
// page type is inferred from builder
ButtonPageView(lesson: Lesson(lessonName: "Second", lessonPage: {
Text("Second Lesson")
}))
}
How can I define ShapeBuilder for SwiftUI
First we should ask whether it's worth writing a ShapeBuilder
instead of writing an AnyShape
type like this:
public struct AnyShape: Shape {
public let makePath: (CGRect) -> Path
public init(makePath: @escaping (CGRect) -> Path) {
self.makePath = makePath
}
public init<Wrapped: Shape>(_ shape: Wrapped) {
self.makePath = { shape.path(in: $0) }
}
public func path(in rect: CGRect) -> Path {
return makePath(rect)
}
}
We can use AnyShape
to write your testForShape
function like this:
func testForShape(value: Bool) -> AnyShape {
if value {
return AnyShape(Circle())
}
else {
return AnyShape(Rectangle())
}
}
I've used AnyShape
in various projects.
Anyway, if we want to write ShapeBuilder
, we can start with the simplest possible implementation, which only supports a body containing a single, unconditional child shape:
@resultBuilder
public struct ShapeBuilder {
public static func buildBlock<C0: Shape>(_ c0: C0) -> C0 { c0 }
}
We can now use it like this:
@ShapeBuilder
func shape1() -> some Shape {
Circle()
}
Hooray!
We can extend it to support a body containing no child shapes by adding a zero-argument buildBlock
and an EmptyShape
type for it to return:
extension ShapeBuilder {
public static func buildBlock() -> EmptyShape { EmptyShape() }
}
public struct EmptyShape: Shape {
public func path(in rect: CGRect) -> Path { Path() }
}
Now we can write the following function without errors:
@ShapeBuilder
func shape0() -> some Shape {
}
To support an if
statement without an else
clause, we add a buildOptional
method and an OptionalShape
type for it to return:
extension ShapeBuilder {
public static func buildOptional<C0: Shape>(_ c0: C0?) -> OptionalShape<C0> {
return OptionalShape(c0)
}
}
public struct OptionalShape<Content: Shape>: Shape {
public let content: Content?
public init(_ content: Content?) {
self.content = content
}
public func path(in rect: CGRect) -> Path {
return content?.path(in: rect) ?? Path()
}
}
Now we can use it like this:
@ShapeBuilder
func shape2(flag: Bool) -> some Shape {
if flag {
Circle()
}
}
To support an if
statement with an else
clause, and to support a switch
statement, we add buildEither
and an EitherShape
type for it to return:
extension ShapeBuilder {
public static func buildEither<First: Shape, Second: Shape>(first: First) -> EitherShape<First, Second> {
return .first(first)
}
public static func buildEither<First: Shape, Second: Shape>(second: Second) -> EitherShape<First, Second> {
return .second(second)
}
}
public enum EitherShape<First: Shape, Second: Shape>: Shape {
case first(First)
case second(Second)
public func path(in rect: CGRect) -> Path {
switch self {
case .first(let first): return first.path(in: rect)
case .second(let second): return second.path(in: rect)
}
}
}
We can now write this function:
@ShapeBuilder
func shape3(_ n: Int) -> some Shape {
if n < 0 {
Rectangle()
} else {
switch n {
case 0:
EmptyShape()
case 1:
Rectangle()
default:
Capsule()
}
}
}
We can support combining two child shapes by writing a buildBlock
method that takes two arguments, and a Tuple2Shape
for it to return:
extension ShapeBuilder {
public static func buildBlock<C0: Shape, C1: Shape>(_ c0: C0, _ c1: C1) -> Tuple2Shape<C0, C1> {
return Tuple2Shape(c0, c1)
}
}
public struct Tuple2Shape<C0: Shape, C1: Shape>: Shape {
public let tuple: (C0, C1)
public init(_ c0: C0, _ c1: C1) {
tuple = (c0, c1)
}
public func path(in rect: CGRect) -> Path {
var path = tuple.0.path(in: rect)
path.addPath(tuple.1.path(in: rect))
return path
}
}
Now we can write this function:
@ShapeBuilder
func shape4() -> some Shape {
Circle()
Rectangle()
}
We can support combining two child shapes by writing a buildBlock
method that takes three arguments, and a Tuple3Shape
for it to return:
extension ShapeBuilder {
public static func buildBlock<C0: Shape, C1: Shape, C2: Shape>(_ c0: C0, _ c1: C1, _ c2: C2) -> Tuple3Shape<C0, C1, C2> {
return Tuple3Shape(c0, c1, c2)
}
}
public struct Tuple3Shape<C0: Shape, C1: Shape, C2: Shape>: Shape {
public let tuple: (C0, C1, C2)
public init(_ c0: C0, _ c1: C1, _ c2: C2) {
tuple = (c0, c1, c2)
}
public func path(in rect: CGRect) -> Path {
var path = tuple.0.path(in: rect)
path.addPath(tuple.1.path(in: rect))
path.addPath(tuple.2.path(in: rect))
return path
}
}
That lets us write this function:
@ShapeBuilder
func shape5() -> some Shape {
Circle()
Rectangle()
Capsule()
}
ViewBuilder
has buildBlock
methods up to arity 10, but I'm not going to write out any more. You can do it yourself if you need them.
Anyway, here's the ShapeBuilder
implementation all together for easy copy'n'paste:
@resultBuilder
public struct ShapeBuilder {
public static func buildBlock<C0: Shape>(_ c0: C0) -> C0 { c0 }
}
extension ShapeBuilder {
public static func buildBlock() -> EmptyShape { EmptyShape() }
}
public struct EmptyShape: Shape {
public func path(in rect: CGRect) -> Path { Path() }
}
extension ShapeBuilder {
public static func buildOptional<C0: Shape>(_ c0: C0?) -> OptionalShape<C0> {
return OptionalShape(c0)
}
}
public struct OptionalShape<Content: Shape>: Shape {
public let content: Content?
public init(_ content: Content?) {
self.content = content
}
public func path(in rect: CGRect) -> Path {
return content?.path(in: rect) ?? Path()
}
}
extension ShapeBuilder {
public static func buildEither<First: Shape, Second: Shape>(first: First) -> EitherShape<First, Second> {
return .first(first)
}
public static func buildEither<First: Shape, Second: Shape>(second: Second) -> EitherShape<First, Second> {
return .second(second)
}
}
public enum EitherShape<First: Shape, Second: Shape>: Shape {
case first(First)
case second(Second)
public func path(in rect: CGRect) -> Path {
switch self {
case .first(let first): return first.path(in: rect)
case .second(let second): return second.path(in: rect)
}
}
}
extension ShapeBuilder {
public static func buildBlock<C0: Shape, C1: Shape>(_ c0: C0, _ c1: C1) -> Tuple2Shape<C0, C1> {
return Tuple2Shape(c0, c1)
}
}
public struct Tuple2Shape<C0: Shape, C1: Shape>: Shape {
public let tuple: (C0, C1)
public init(_ c0: C0, _ c1: C1) {
tuple = (c0, c1)
}
public func path(in rect: CGRect) -> Path {
var path = tuple.0.path(in: rect)
path.addPath(tuple.1.path(in: rect))
return path
}
}
extension ShapeBuilder {
public static func buildBlock<C0: Shape, C1: Shape, C2: Shape>(_ c0: C0, _ c1: C1, _ c2: C2) -> Tuple3Shape<C0, C1, C2> {
return Tuple3Shape(c0, c1, c2)
}
}
public struct Tuple3Shape<C0: Shape, C1: Shape, C2: Shape>: Shape {
public let tuple: (C0, C1, C2)
public init(_ c0: C0, _ c1: C1, _ c2: C2) {
tuple = (c0, c1, c2)
}
public func path(in rect: CGRect) -> Path {
var path = tuple.0.path(in: rect)
path.addPath(tuple.1.path(in: rect))
path.addPath(tuple.2.path(in: rect))
return path
}
}
Related Topics
"Ambiguous Use of 'Children'" When Trying to Use Nstreecontroller.Arrangedobjects in Swift 3.0
Create a Weak Container in Swift That Accepts a Native Swift Protocol
Persist Accessibility Permissions Between Builds in Xcode 13
How to Add Characters into Dateformatter
Swift: Skspritekit, Using Storyboards, Uiviewcontroller and Uibutton to Set in Game Parameters
Example for Drag and Drop Inside Nscollectionview
Swift 2 Unrecognized Selector in Tapgesture
Uibarbuttonitem Action Not Work Uitableview Cell
Swift Uitableviewautomaticdimension Is Not Working
Simple Swift Class Does Not Compile
How to Display an Image by an API Url? Swift
Autoscrolling Infinite Effect in .Linear Type of Icarousel in Swift
Exc Bad Access After Coding Signing
Is There Any Particular Use of Closure in Swift? and What's the Benefit
Get Images from Document Directory Not File Path Swift 3
Handle Single Click and Double Click While Updating the View