Skip to content

Generic SettingView #23

Description

@aheze

The problem

Currently SettingView renders each element via a huge switch.

Huge `switch` statement that checks if the element is a text, button, toggle, page, etc.

This has several limitations:

No support for custom Settings

The switch statement checks against SettingText, SettingButton, SettingToggle, etc. Say we had our own element called SettingLabel:

struct SettingLabel: View, Setting {
    var id: AnyHashable?

    var body: some View {
        Text("Hello!")
    }
}

This would fall through in the switch statement and error out.

switch setting {
case let text as SettingText:
    text
case let button as SettingButton:
    button
case let toggle as SettingToggle:
    /* ... */
default:
    Text("Unsupported setting, please file a bug report.")
}

Text displaying "Unsupported setting, please file a bug report"

Limited customization (no subclass / composing support)

This is related to the above issue. Let's say you want to make a bunch of SettingTexts blue. Right now you need to do this:

SettingText(title: "Hello", foregroundColor: .blue)
SettingText(title: "Hello again", foregroundColor: .blue)
SettingText(title: "Bruh", foregroundColor: .blue)

Ideally you'd be able to make a new element that conforms from Setting.

struct BlueSettingText: View, Setting {
    var id: AnyHashable?
    var title: String

    var body: some View {
        SettingText(title: title, foregroundColor: .blue)
    }
}

/* ... */

BlueSettingText(title: "Hello")
BlueSettingText(title: "Hello again")
BlueSettingText(title: ":)")

This code compiles, but it will fall through in the above mentioned issue with the switch statement.

Reliance on stable ID identifiers

You might have noticed all the var id: AnyHashable?s floating around. This is because of the ForEach loop for SettingPage, SettingGroup, and SettingTupleView.

ForEach(page.tuple.settings, id: \.identifier) { setting in
    SettingView(setting: setting, isPagePreview: true)
}

ForEach(group.tuple.settings, id: \.identifier) { setting in
    SettingView(setting: setting)
}

ForEach(tuple.settings, id: \.identifier) { setting in
    SettingView(setting: setting)
}

Looping over each element's children, for example `page.tuple.settings`

Currently, Setting synthesizes this ID depending on the element — for SettingText, it gets the title property. For SettingTupleView, it gets tuple.flattened.compactMap { $0.textIdentifier }.joined().

https://github.com/aheze/Setting/blob/main/Sources/Setting.swift#L18-L38

Ideally, we could get rid of this ID and just use a View's default identity. (For more details, see How the SwiftUI View Lifecycle and Identity work, "Identity of a view" section.)

No support for custom view modifiers

SettingToggle(title: "This value is persisted!", isOn: $isOn)
    .tint(.red) /// Argument type 'some View' does not conform to expected type 'Setting'

This requires changes to SettingBuilder.

The solution

SettingView needs to be generic, resembling SwiftUI's View as closely as possible.

If possible, we should make Setting conform to View, so that we can render it directly without the need for a huge switch.

I've attempted this and got lost quickly — I'm not good with generics.

Any help? :)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions