ra1028 / Carbon

🚴 A declarative library for building component-based user interfaces in UITableView and UICollectionView.
https://ra1028.github.io/Carbon
Apache License 2.0
1.33k stars 97 forks source link

Generic parameter 'Element' could not be inferred #53

Closed wousser closed 5 years ago

wousser commented 5 years ago

Checklist

Expected Behavior

While updating from RC2 to RC4, the next issue happens.

Current Behavior

Array of class [ClassName], using the group function to list as cells.

cells: {
                        Group(of: state.todos.enumerated()) { offset, todo in
                            TodoText(todo: todo, isCompleted: false)
}}

Detailed Description (Include Screenshots)

However, using another class results in this error: Generic parameter 'Element' could not be inferred. Explicitly specify the generic arguments to fix this issue When explicitly specifying arguments I get the following error: Unable to infer closure type in the current context.

What am I doing wrong?

Environment

ra1028 commented 5 years ago

@wousser

The compiler may have output an incorrect error. Is TodoText conform to Identifiable Component? TodoText(todo: todo, isCompleted: false).identified(by: \.todo.id) Isn't there any other parameters wrong?

BTW, thank you for opening some issues. However, they were the general question related to the Swift type system, so please try-and-error by referring to the API doc before opening the issue.

wousser commented 5 years ago

Thanks for your quick answers.

The reason I'm asking here is that it worked with RC2, I didn't expect any breaking changes in an RC.

I'm trying to rewrite this part:

struct UpcomingEvent {
    typealias ID = UUID

    var id: ID
    var date: Date
    var events: [Any] // Contact, (Reminder, Contact), (Date, Contact)
}
struct State {
  var upcomingEvents = [UpcomingEvent]()
}
Section(id: "upcoming") { section in
                section.header = ViewNode(UpcomingHeader())

                state.upcomingEvents.forEach({ event in
                    section.cells.append(
                        CellNode(UpcomingDateCard(date: event.date))
                    )

                    event.events.forEach({ (eventType) in

                        //contact
                        if let contact = eventType as? Contact {
                            section.cells.append(
                                CellNode(UpcomingContactCellNode(contact: contact))
                            )
                        }

                        //reminder
                        if let reminder = eventType as? (Reminder, Contact) {
                            section.cells.append(
                                CellNode(UpcomingReminderCellNode(reminder: reminder))
                            )
                        }

                        //birthday
                        if let birthday = eventType as? (Date, Contact) {
                            section.cells.append(
                                CellNode(UpcomingBirthday(birthday: birthday))
                            )
                        }
                    })
                })
        }

All CellNode's conform to Identifiable Component.

I tried to rewrite it as Group(of: ) but wasn't able to. Maybe you can share the best way to go ahead?

ra1028 commented 5 years ago

Dynamic type casting isn't compatible with function builder because if let and switch statements are not allowed. I recommended to rewrite using enum, but you can also take the following workaround with legacy API. This hasn't actually try to compiled, but it probably works.

Section(
    id: "upcoming",
    header: ViewNode(UpcomingHeader()),
    cells: state.upcomingEvents.flatMap { event -> [CellNode] in
        event.events.compactMap { eventType -> CellNode? in
            //contact
            if let contact = eventType as? Contact {
                return CellNode(UpcomingContact(contact: contact))
            }

            //reminder
            if let reminder = eventType as? (Reminder, Contact) {
                return CellNode(UpcomingReminder(reminder: reminder))
            }

            //birthday
            if let birthday = eventType as? (Date, Contact) {
                return CellNode(UpcomingBirthday(birthday: birthday))
            }

            return  nil
        }
    }
)
ra1028 commented 5 years ago

Another approach with function builder.

func component(for eventType: Any) -> AnyComponent? {
    //contact
    if let contact = eventType as? Contact {
        return AnyComponent(UpcomingContactCellNode(contact: contact))
    }

    //reminder
    if let reminder = eventType as? (Reminder, Contact) {
        return AnyComponent(UpcomingReminderCellNode(reminder: reminder))
    }

    //birthday
    if let birthday = eventType as? (Date, Contact) {
        return AnyComponent(UpcomingBirthday(birthday: birthday))
    }

    return  nil
}

Section(
    id: "upcoming",
    header:UpcomingHeader(),
    cells: {
        Group(of: state.upcomingEvents) { event in
            Group(of: event.events.compactMap { eventType in
                component(for: eventType)
            }
        }
    }
)
wousser commented 5 years ago

Thanks for your help. That approach works great.