diff --git a/Example/OpenSwiftUIUITests/View/GroupUITests.swift b/Example/OpenSwiftUIUITests/View/GroupUITests.swift new file mode 100644 index 000000000..f7adb06ed --- /dev/null +++ b/Example/OpenSwiftUIUITests/View/GroupUITests.swift @@ -0,0 +1,26 @@ +// +// GroupUITests.swift +// OpenSwiftUIUITests + +import Testing +import SnapshotTesting + +@MainActor +@Suite(.snapshots(record: .never, diffTool: diffTool)) +struct GroupUITests { + @Test + func groupColor() { + struct ContentView: View { + var body: some View { + Group { + Color.red + Group { + Color.blue + Color.green + } + }.frame(width: 100, height: 100) + } + } + openSwiftUIAssertSnapshot(of: ContentView()) + } +} diff --git a/Sources/OpenSwiftUICore/View/Group.swift b/Sources/OpenSwiftUICore/View/Group.swift new file mode 100644 index 000000000..34762a07f --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Group.swift @@ -0,0 +1,538 @@ +// +// Group.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete +// ID: C1B8B6896BB94C69479F427820712D02 (SwiftUICore) + +package import OpenAttributeGraphShims + +// MARK: - Group + +/// A type that collects multiple instances of a content type --- like views, +/// scenes, or commands --- into a single unit. +/// +/// Use a group to collect multiple views into a single instance, without +/// affecting the layout of those views, like an ``OpenSwiftUI/HStack``, +/// ``OpenSwiftUI/VStack``, or ``OpenSwiftUI/Section`` would. After creating a group, +/// any modifier you apply to the group affects all of that group's members. +/// For example, the following code applies the ``OpenSwiftUI/Font/headline`` +/// font to three views in a group. +/// +/// Group { +/// Text("OpenSwiftUI") +/// Text("Combine") +/// Text("Swift System") +/// } +/// .font(.headline) +/// +/// Because you create a group of views with a ``OpenSwiftUI/ViewBuilder``, you can +/// use the group's initializer to produce different kinds of views from a +/// conditional, and then optionally apply modifiers to them. The following +/// example uses a `Group` to add a navigation bar title, +/// regardless of the type of view the conditional produces: +/// +/// Group { +/// if isLoggedIn { +/// WelcomeView() +/// } else { +/// LoginView() +/// } +/// } +/// .navigationBarTitle("Start") +/// +/// The modifier applies to all members of the group --- and not to the group +/// itself. For example, if you apply ``View/onAppear(perform:)`` to the above +/// group, it applies to all of the views produced by the `if isLoggedIn` +/// conditional, and it executes every time `isLoggedIn` changes. +/// +/// Because a group of views itself is a view, you can compose a group within +/// other view builders, including nesting within other groups. This allows you +/// to add large numbers of views to different view builder containers. The +/// following example uses a `Group` to collect 10 ``OpenSwiftUI/Text`` instances, +/// meaning that the vertical stack's view builder returns only two views --- +/// the group, plus an additional ``OpenSwiftUI/Text``: +/// +/// var body: some View { +/// VStack { +/// Group { +/// Text("1") +/// Text("2") +/// Text("3") +/// Text("4") +/// Text("5") +/// Text("6") +/// Text("7") +/// Text("8") +/// Text("9") +/// Text("10") +/// } +/// Text("11") +/// } +/// } +/// +/// You can initialize groups with several types other than ``OpenSwiftUI/View``, +/// such as ``OpenSwiftUI/Scene`` and ``OpenSwiftUI/ToolbarContent``. The closure you +/// provide to the group initializer uses the corresponding builder type +/// (``OpenSwiftUI/SceneBuilder``, ``OpenSwiftUI/ToolbarContentBuilder``, and so on), +/// and the capabilities of these builders vary between types. For example, +/// you can use groups to return large numbers of scenes or toolbar content +/// instances, but not to return different scenes or toolbar content based +/// on conditionals. +@available(OpenSwiftUI_v1_0, *) +@frozen +public struct Group { + + public typealias Body = Never + + @usableFromInline + package var content: Content + + @_disfavoredOverload + @_alwaysEmitIntoClient + internal init(_content: Content) { + self.content = _content + } +} + +@available(*, unavailable) +extension Group: Sendable {} + +@available(OpenSwiftUI_v1_0, *) +extension Group { + + + @available(OpenSwiftUI_v1_0, *) + @_alwaysEmitIntoClient + public static func _make(content: Content) -> Group { + self.init(_content: content) + } +} + +@available(OpenSwiftUI_v1_0, *) +extension Group: View, MultiView, PrimitiveView where Content: View { + + /// Creates a group of views. + /// - Parameter content: A ``OpenSwiftUI/ViewBuilder`` that produces the views + /// to group. + @inlinable + nonisolated public init(@ViewBuilder content: () -> Content) { + self.content = content() + } + + nonisolated public static func _makeViewList( + view: _GraphValue, + inputs: _ViewListInputs + ) -> _ViewListOutputs { + _VariadicView.Tree.makeDebuggableViewList( + view: view.unsafeBitCast(to: _VariadicView.Tree.self), + inputs: inputs + ) + } + + @available(OpenSwiftUI_v2_0, *) + nonisolated public static func _viewListCount(inputs: _ViewListCountInputs) -> Int? { + Content._viewListCount(inputs: inputs) + } +} + +// MARK: - _ViewListOutputs + Group + +extension _ViewListOutputs { + package static func sectionListOutputs( + _ outputs: [_ViewListOutputs], + inputs: _ViewListInputs + ) -> _ViewListOutputs { + var nextImplicitID = inputs.implicitID + var staticCount: Int? = 0 + var lists: [Attribute] = [] + for output in outputs { + let list = output.makeAttribute(inputs: inputs) + lists.append(list) + nextImplicitID = output.nextImplicitID + if let currentStaticCount = staticCount, + let outputStaticCount = output.staticCount { + staticCount = outputStaticCount &+ currentStaticCount + } else { + staticCount = nil + } + } + if inputs.options.contains(.sectionsConcatenateFooter) { + lists[1] = Attribute(ViewList.Group.Init(lists: .init(lists.dropFirst()))) + lists[2] = inputs.base.intern(EmptyViewList(), id: .defaultValue) + } + let section = Attribute( + MakeSection( + lists: lists, + isHierarchical: inputs.options.contains(.sectionsAreHierarchical), + traits: inputs._traits + ) + ) + return _ViewListOutputs( + .dynamicList(section, nil), + nextImplicitID: nextImplicitID, + staticCount: staticCount + ) + } + + @inline(__always) + private static func groupViewListOptions( + from options: _ViewListInputs.Options + ) -> _ViewListInputs.Options { + var optionsWithoutNestedSections = options + if optionsWithoutNestedSections.contains(.requiresNonEmptyGroupParent) { + optionsWithoutNestedSections.subtract([.requiresNonEmptyGroupParent, .allowsNestedSections]) + } + if optionsWithoutNestedSections.contains(.requiresSections) { + optionsWithoutNestedSections.subtract([.requiresSections, .allowsNestedSections]) + } + if options.contains(.allowsNestedSections) { + return options + } else { + return optionsWithoutNestedSections + } + } + + package static func groupViewList( + parent: _GraphValue, + footer: Attribute