Can't Use 'Shape' as Type in Swiftui

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.

  1. In the simplified example below I use a @ViewBuilder to clean up the code that we are returning. There is no need to use AnyView 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 each Shape in the getShape
    function, otherwise we could have made the extension on Shape
    instead of View.

  2. 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 modifiers frame and rotationEffect.

  3. The @ViewBuilder getShape(shape:index:) returns the the shape you have picked with its chosen color, this is then used by the function createShape(shape:index:) where you add the custom modifier that we created as an extension on View.

  4. 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



Leave a reply



Submit