Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,15 @@ open class UIHostingController<Content> : UIViewController where Content : View
get { host.rootView }
_modify { yield &host.rootView }
}

public func _forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void) {
host._forEachIdentifiedView(body: body)
}
}

@available(macOS, unavailable)
extension UIHostingController: _UIHostingViewable where Content == AnyView {

}

@available(macOS, unavailable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
// ID: FAF0B683EB49BE9BABC9009857940A1E

#if os(iOS)
@_spi(ForOpenSwiftUIOnly) public import OpenSwiftUICore
@_spi(ForOpenSwiftUIOnly)
@_spi(Private)
public import OpenSwiftUICore
public import UIKit

@available(macOS, unavailable)
Expand Down Expand Up @@ -151,6 +153,19 @@ open class _UIHostingView<Content>: UIView where Content: View {
// TODO
func clearUpdateTimer() {
}

func _forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void) {
let tree = preferenceValue(_IdentifiedViewsKey.self)
let adjustment = { [weak self](rect: inout CGRect) in
guard let self else { return }
rect = convert(rect, from: nil)
}
tree.forEach { proxy in
var proxy = proxy
proxy.adjustment = adjustment
body(proxy)
}
}
}

extension _UIHostingView: ViewRendererHost {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Foundation
public protocol _UIHostingViewable: AnyObject {
// var rootView: AnyView { get set }
// func _render(seconds: Double)
// func _forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void)
func _forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void)
// func sizeThatFits(in size: CGSize) -> CGSize
// var _disableSafeArea: Bool { get set }
//// var _rendererConfiguration: _RendererConfiguration { get set }
Expand Down
3 changes: 3 additions & 0 deletions Sources/OpenSwiftUI/Test/TestApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
// Status: WIP
// ID: A519B5B95CA8FF4E3445832668F0B2D2

@_spi(Testing)
import OpenSwiftUICore

public struct _TestApp {
public init() {
preconditionFailure("TODO")
Expand Down
39 changes: 0 additions & 39 deletions Sources/OpenSwiftUI/Test/TestIDView.swift

This file was deleted.

28 changes: 28 additions & 0 deletions Sources/OpenSwiftUI/View/IdentifiedView/IdentifiedViewTree.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// IdentifiedViewTree.swift
// OpenSwiftUI
//
// Audited for iOS 18.0
// Status: Complete

public enum _IdentifiedViewTree {
case empty
case proxy(_IdentifiedViewProxy)
case array([_IdentifiedViewTree])

public func forEach(_ body: (_IdentifiedViewProxy) -> Void) {
switch self {
case .empty:
break
case let .proxy(proxy):
body(proxy)
case let .array(array):
for treeElement in array {
treeElement.forEach(body)
}
}
}
}

@available(*, unavailable)
extension _IdentifiedViewTree: Sendable {}
39 changes: 39 additions & 0 deletions Sources/OpenSwiftUI/View/IdentifiedView/IdentifiedViewsKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// IdentifiedViewsKey.swift
// OpenSwiftUI
//
// Audited for iOS 18.0
// Status: Complete

@_spi(Private)
public import OpenSwiftUICore

public struct _IdentifiedViewsKey {
public typealias Value = _IdentifiedViewTree

public static let defaultValue: _IdentifiedViewTree = .empty

public static func reduce(value: inout _IdentifiedViewTree, nextValue: () -> _IdentifiedViewTree) {
let newValue = nextValue()
switch (value, newValue) {
case (_, .empty):
break
case (.empty, _):
value = newValue
case let (.proxy(oldProxy), .proxy(newProxy)):
value = .array([.proxy(oldProxy)] + [.proxy(newProxy)])
case let (.array(oldArray), .proxy(newProxy)):
value = .array(oldArray + [.proxy(newProxy)])
case let (.proxy(oldProxy), .array(newArray)):
value = .array([.proxy(oldProxy)] + newArray)
case let (.array(oldArray), .array(newArray)):
value = .array(oldArray + newArray)
}
}
}

@available(*, unavailable)
extension _IdentifiedViewsKey: Sendable {}

@_spi(Private)
extension _IdentifiedViewsKey: HostPreferenceKey {}
7 changes: 7 additions & 0 deletions Sources/OpenSwiftUICore/Data/Preference/PreferenceKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,19 @@ public protocol PreferenceKey {
/// - nextValue: A closure that returns the next value in the sequence.
static func reduce(value: inout Value, nextValue: () -> Value)

/// If true `reduce()` will also see preference values for views
/// that have active removal transitions. The default
/// implementation returns false.
static var _includesRemovedValues: Bool { get }

/// If true the preference may be read via the renderer host API.
/// Defaults to false. If true `_includesRemovedValues` should be
/// false.
static var _isReadableByHost: Bool { get }
}

extension PreferenceKey where Value: ExpressibleByNilLiteral {
/// Let nil-expressible values default-initialize to nil.
public static var defaultValue: Value { Value(nilLiteral: ()) }
}

Expand Down
4 changes: 3 additions & 1 deletion Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ extension ViewRendererHost {

@_spi(Private)
public func preferenceValue<K>(_ key: K.Type) -> K.Value where K: HostPreferenceKey {
preconditionFailure("TODO")
updateViewGraph { graph in
graph.preferenceValue(key)
}
}

package func idealSize() -> CGSize { preconditionFailure("TODO") }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// IdentifiedViewProxy.swift
// OpenSwiftUICore
//
// Audited for iOS 18.0
// Status: Complete

public import Foundation

public struct _IdentifiedViewProxy {
public var identifier: AnyHashable
package var size: CGSize
package var position: CGPoint
package var transform: ViewTransform
package var adjustment: ((inout CGRect) -> ())?
package var accessibilityNodeStorage: Any?
package var platform: _IdentifiedViewProxy.Platform

package init(identifier: AnyHashable, size: CGSize, position: CGPoint, transform: ViewTransform, accessibilityNode: Any?, platform: _IdentifiedViewProxy.Platform) {
self.identifier = identifier
self.size = size
self.position = position
self.transform = transform
self.accessibilityNodeStorage = accessibilityNode
self.platform = platform
}

public var boundingRect: CGRect {
var rect = CGRect(origin: .zero, size: size)
rect.convert(to: .global, transform: transform.withPosition(position))
adjustment?(&rect)
return rect
}
}

@available(*, unavailable)
extension _IdentifiedViewProxy: Sendable {}

package struct IdentifiedViewPlatformInputs {
package init(inputs: _ViewInputs, outputs: _ViewOutputs) {}
}

extension _IdentifiedViewProxy {
package struct Platform {
package init(_ inputs: IdentifiedViewPlatformInputs) {}
}
}

package protocol IdentifierProvider {
func matchesIdentifier<I>(_ identifier: I) -> Bool where I: Hashable
}
10 changes: 0 additions & 10 deletions Sources/OpenSwiftUICore/View/IdentifiedViewProxy.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Sources/OpenSwiftUICore/View/Input/ViewOutputs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public struct _ViewOutputs {

private var _layoutComputer: OptionalAttribute<LayoutComputer>

init() {
package init() {
preferences = PreferencesOutputs()
_layoutComputer = OptionalAttribute()
}
Expand Down
63 changes: 63 additions & 0 deletions Sources/OpenSwiftUICore/View/Test/TestIDView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// TestIDView.swift
// OpenSwiftUICore
//
// Audited for iOS 18.0
// Status: WIP
// ID: CC151E1A36B4405FF56CDABA5D46BF1E

import OpenGraphShims

@_spi(Testing)
extension View {
nonisolated public func testID<ID>(_ id: ID) -> TestIDView<Self, ID> where ID : Hashable {
TestIDView(content: self, id: id)
}
}

@_spi(Testing)
@MainActor
@preconcurrency
public struct TestIDView<Content, ID>: PrimitiveView, UnaryView where Content: View, ID: Hashable {
public var content: Content
public var id: ID

nonisolated public static func _makeView(view: _GraphValue<Self>, inputs: _ViewInputs) -> _ViewOutputs {
fatalError()
}

public typealias Body = Never

private struct IdentifiedView: StatefulRule, AsyncAttribute, IdentifierProvider, CustomStringConvertible {
@Attribute var view: TestIDView
var id: ID?

init(view: Attribute<TestIDView>, id: ID?) {
self._view = view
self.id = id
}

// TODO
typealias Value = TestIDView

mutating func updateValue() {
// TODO: id = view.id
}

func matchesIdentifier<I>(_ identifier: I) -> Bool where I: Hashable {
compareValues(id, identifier as? ID)
}

var description: String {
if let id {
"ID: \(id)"
} else {
"ID"
}
}
}
}

@_spi(Testing)
@available(*, unavailable)
extension TestIDView: Sendable {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// IdentifiedViewProxyTests.swift
// OpenSwiftUICompatibilityTests

import Testing
#if os(iOS)
import UIKit
#endif

@MainActor
struct IdentifiedViewProxyTests {
@Test
func boundingRect() async {
#if os(iOS) && OPENSWIFTUI_COMPATIBILITY_TEST // FIXME: add _identified modifier
let identifier = "Test"
let hosting = UIHostingController(rootView: AnyView(EmptyView())._identified(by: identifier))
await confirmation { @MainActor confirmation in
hosting._forEachIdentifiedView { proxy in
confirmation()
#expect(proxy.identifier == AnyHashable(identifier))
#expect(proxy.boundingRect == .zero)
}
}
#endif
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// IdentifiedViewTreeTests.swift
// OpenSwiftUICompatibilityTests

import Testing

struct IdentifiedViewTreeTests {
@Test
func forEachEmpty() async {
let tree = _IdentifiedViewTree.empty
await confirmation(expectedCount: 0) { confirm in
tree.forEach { _ in
confirm()
}
}
}
}
Loading
Loading