diff --git a/.gitignore b/.gitignore index d636de2..9520fd8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /.build /Packages /*.xcodeproj +/*.xcworkspace xcuserdata/ DerivedData/ .swiftpm/config/registries.json diff --git a/.mise.toml b/.mise.toml new file mode 100644 index 0000000..2b6e6a0 --- /dev/null +++ b/.mise.toml @@ -0,0 +1,2 @@ +[tools] +tuist = "4.18" diff --git a/Development/Development.xcodeproj/project.pbxproj b/Development/Development.xcodeproj/project.pbxproj index d237614..2968f9a 100644 --- a/Development/Development.xcodeproj/project.pbxproj +++ b/Development/Development.xcodeproj/project.pbxproj @@ -350,7 +350,7 @@ PRODUCT_BUNDLE_IDENTIFIER = app.muukii.Development; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -380,7 +380,7 @@ PRODUCT_BUNDLE_IDENTIFIER = app.muukii.Development; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/Development/Development/BookUIKitBasedCompositional.swift b/Development/Development/BookUIKitBasedCompositional.swift index 48f0286..047e47b 100644 --- a/Development/Development/BookUIKitBasedCompositional.swift +++ b/Development/Development/BookUIKitBasedCompositional.swift @@ -4,7 +4,9 @@ import DynamicList import os import MondrianLayout -fileprivate var globalCount: Int = 0 + +nonisolated(unsafe) fileprivate var globalCount: Int = 0 + fileprivate func getGlobalCount() -> Int { globalCount &+= 1 return globalCount diff --git a/Development/Development/BookUIKitBasedFlow.swift b/Development/Development/BookUIKitBasedFlow.swift index 76d3e76..418c510 100644 --- a/Development/Development/BookUIKitBasedFlow.swift +++ b/Development/Development/BookUIKitBasedFlow.swift @@ -4,7 +4,7 @@ import DynamicList import os -fileprivate var globalCount: Int = 0 +nonisolated(unsafe) fileprivate var globalCount: Int = 0 fileprivate func getGlobalCount() -> Int { globalCount &+= 1 return globalCount @@ -101,6 +101,7 @@ struct BookUIKitBasedFlow: View, PreviewProvider { case .a(let v): return context.cell(reuseIdentifier: "A") { state, _ in ComposableCell { + TextField("Hello", text: .constant("Hoge")) HStack { Text("\(state.isHighlighted.description)") Text("\(v.name)") diff --git a/Development/Development/BookVariadicView.swift b/Development/Development/BookVariadicView.swift index d7a85ee..bb96f02 100644 --- a/Development/Development/BookVariadicView.swift +++ b/Development/Development/BookVariadicView.swift @@ -9,13 +9,6 @@ struct BookVariadicView: View, PreviewProvider { List { - NavigationLink { - BackedContent() - .navigationTitle("Backed") - } label: { - Text("Backed") - } - NavigationLink { NativeContent() .navigationTitle("Native") @@ -31,13 +24,6 @@ struct BookVariadicView: View, PreviewProvider { NavigationView { List { - NavigationLink { - BackedContent() - .navigationTitle("Backed") - } label: { - Text("Backed") - } - NavigationLink { NativeContent() .navigationTitle("Native") @@ -69,62 +55,6 @@ struct BookVariadicView: View, PreviewProvider { } } - private struct BackedContent: View { - - @State var items: [Message] = MockData.randomMessages(count: 2000) - - var body: some View { - VStack { - CustomList { - ForEach( - items, - content: { - ComplexCell(message: $0) - } - ) - } - } - } - - struct Cell1: View { - - @State var flag = false - - var body: some View { - VStack { - HStack { - Text("Hello") - Toggle("Flag", isOn: $flag) - } - Rectangle() - .frame(height: flag ? 10 : 50) - } - .padding(.horizontal, 20) - .padding(.vertical, 30) - } - } - - struct Cell2: View { - - @State var flag = false - - var body: some View { - VStack { - HStack { - Text("Hello") - Toggle("Flag", isOn: $flag) - } - Rectangle() - .fill(Color.red) - .frame(height: flag ? 10 : 50) - } - .padding(.horizontal, 20) - .padding(.vertical, 30) - } - } - - } - static let url = URL( string: "https://images.unsplash.com/photo-1686726754283-3cf793dec0e6?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80" diff --git a/Sources/DynamicList/ContentPagingTrigger.swift b/Sources/DynamicList/ContentPagingTrigger.swift index 7b649c7..4662239 100644 --- a/Sources/DynamicList/ContentPagingTrigger.swift +++ b/Sources/DynamicList/ContentPagingTrigger.swift @@ -60,14 +60,14 @@ final class ContentPagingTrigger { self.leadingScreensForBatching = leadingScreensForBatching self.trackingScrollDirection = trackingScrollDirection - offsetObservation = scrollView.observe(\.contentOffset, options: [.initial, .new]) { - @MainActor(unsafe) [weak self] scrollView, _ in + offsetObservation = scrollView.observe(\.contentOffset, options: [.initial, .new]) { [weak self] scrollView, _ in guard let `self` = self else { return } - self.didScroll(scrollView: scrollView) + MainActor.assumeIsolated { + self.didScroll(scrollView: scrollView) + } } - contentSizeObservation = scrollView.observe(\.contentSize, options: [.initial, .new]) { - @MainActor(unsafe) scrollView, _ in + contentSizeObservation = scrollView.observe(\.contentSize, options: [.initial, .new]) { scrollView, _ in // print(scrollView.contentSize) } } diff --git a/Sources/DynamicList/DynamicList.swift b/Sources/DynamicList/DynamicList.swift index 719a84c..09e9a37 100644 --- a/Sources/DynamicList/DynamicList.swift +++ b/Sources/DynamicList/DynamicList.swift @@ -1,12 +1,11 @@ import SwiftUI -import UIKit public enum Selection { case single(Data) case multiple(Set) } -public struct DynamicList: UIViewRepresentable { +public struct DynamicList: UIViewRepresentable { public struct ScrollTarget { public let item: Item diff --git a/Sources/DynamicList/DynamicListView.swift b/Sources/DynamicList/DynamicListView.swift index ac63ad1..35729ad 100644 --- a/Sources/DynamicList/DynamicListView.swift +++ b/Sources/DynamicList/DynamicListView.swift @@ -33,7 +33,9 @@ public protocol CustomStateKey { */ public struct CellState { - public static let empty = CellState() + public static var empty: CellState { + .init() + } private var stateMap: [AnyKeyPath : Any] = [:] @@ -63,7 +65,7 @@ public enum DynamicListViewScrollAction { /// /// - TODO: Currently supported only vertical scrolling. @available(iOS 13, *) -public final class DynamicListView: UIView, +public final class DynamicListView: UIView, UICollectionViewDelegate, UIScrollViewDelegate { @@ -135,7 +137,7 @@ public final class DynamicListView: UIView, if _collectionView.cellForIdentifiers.contains(_reuseIdentifier) == false { - Log.debug(.generic, "Register Cell : \(_reuseIdentifier)") + Log.generic.debug("Register Cell : \(_reuseIdentifier)") _collectionView.register(VersatileCell.self, forCellWithReuseIdentifier: _reuseIdentifier) } @@ -636,12 +638,12 @@ internal final class CollectionView: UICollectionView { public typealias DynamicCompositionalLayoutSingleSectionView = DynamicListView -public enum DynamicCompositionalLayoutSingleSection: Hashable { +public enum DynamicCompositionalLayoutSingleSection: Hashable, Sendable { case main } private enum CellContextKey: EnvironmentKey { - static var defaultValue: VersatileCell? + static var defaultValue: VersatileCell? { nil } } extension EnvironmentValues { diff --git a/Sources/DynamicList/Log.swift b/Sources/DynamicList/Log.swift index c2a7035..580ce98 100644 --- a/Sources/DynamicList/Log.swift +++ b/Sources/DynamicList/Log.swift @@ -2,33 +2,15 @@ import os.log enum Log { - - static func debug( - dso: UnsafeRawPointer = #dsohandle, - file: StaticString = #file, - line: UInt = #line, - _ log: OSLog, - _ object: @autoclosure () -> Any - ) { - os_log(.debug, dso: dso, log: log, "%{public}@", "\(object())") - } - - static func error( - dso: UnsafeRawPointer = #dsohandle, - file: StaticString = #file, - line: UInt = #line, - _ log: OSLog, - _ object: @autoclosure () -> Any - ) { - os_log(.error, dso: dso, log: log, "%{public}@", "\(object())") - } - + + static let generic = Logger(OSLog.makeOSLogInDebug { OSLog.init(subsystem: "DynamicList", category: "generic") }) + } extension OSLog { @inline(__always) - private static func makeOSLogInDebug(isEnabled: Bool = true, _ factory: () -> OSLog) -> OSLog { + fileprivate static func makeOSLogInDebug(isEnabled: Bool = true, _ factory: () -> OSLog) -> OSLog { #if DEBUG return factory() #else @@ -36,5 +18,6 @@ extension OSLog { #endif } - static let generic: OSLog = makeOSLogInDebug { OSLog.init(subsystem: "app.muukii", category: "generic") } } + + diff --git a/Sources/DynamicList/NSDiffableDataSourceSnapshot+Unique.swift b/Sources/DynamicList/NSDiffableDataSourceSnapshot+Unique.swift index 3c2053c..ef9d920 100644 --- a/Sources/DynamicList/NSDiffableDataSourceSnapshot+Unique.swift +++ b/Sources/DynamicList/NSDiffableDataSourceSnapshot+Unique.swift @@ -1,9 +1,9 @@ import UIKit -public enum DiffableDataSourceError: Error { - case duplicatedSectionIdentifiers(Set) - case duplicatedItemIdentifiers(Set) +public enum DiffableDataSourceError: Error, Sendable { + case duplicatedSectionIdentifiers(debugDescription: String) + case duplicatedItemIdentifiers(debugDescription: String) } extension NSDiffableDataSourceSnapshot { @@ -17,7 +17,7 @@ extension NSDiffableDataSourceSnapshot { for section in sections { let (inserted, _) = sectionSet.insert(section) guard inserted else { - throw DiffableDataSourceError.duplicatedSectionIdentifiers([section]) + throw DiffableDataSourceError.duplicatedSectionIdentifiers(debugDescription: String(describing: [section])) } } @@ -28,7 +28,7 @@ extension NSDiffableDataSourceSnapshot { if set.isEmpty { appendSections(sections) } else { - throw DiffableDataSourceError.duplicatedSectionIdentifiers(set) + throw DiffableDataSourceError.duplicatedSectionIdentifiers(debugDescription: String(describing: set)) } } @@ -43,7 +43,7 @@ extension NSDiffableDataSourceSnapshot { for item in items { let (inserted, _) = itemSet.insert(item) guard inserted else { - throw DiffableDataSourceError.duplicatedItemIdentifiers([item]) + throw DiffableDataSourceError.duplicatedItemIdentifiers(debugDescription: String(describing: [item])) } } @@ -53,7 +53,7 @@ extension NSDiffableDataSourceSnapshot { if set.isEmpty { appendItems(items, toSection: sectionIdentifier) } else { - throw DiffableDataSourceError.duplicatedItemIdentifiers(set) + throw DiffableDataSourceError.duplicatedItemIdentifiers(debugDescription: String(describing: set)) } } diff --git a/Sources/DynamicList/swift_dynamic_list.swift b/Sources/DynamicList/swift_dynamic_list.swift index 8a6486a..8b13789 100644 --- a/Sources/DynamicList/swift_dynamic_list.swift +++ b/Sources/DynamicList/swift_dynamic_list.swift @@ -1,99 +1 @@ -import SwiftUI -import UIKit -#if DEBUG - -// Experimental -public struct CustomList: View { - - let tree: _VariadicView.Tree - - public init(@ViewBuilder content: () -> Content) { - self.tree = _VariadicView.Tree(VariadicViewProxy(), content: content) - } - - public var body: some View { - tree - } - - struct VariadicViewProxy: _VariadicView_MultiViewRoot { - @ViewBuilder - func body(children: _VariadicView.Children) -> some View { - _CollectionView(children: children) - } - } - -} - -extension _VariadicView_Children.Element: Hashable { - public static func == (lhs: Self, rhs: Self) -> Bool { - lhs.id == rhs.id - } - - public func hash(into hasher: inout Hasher) { - id.hash(into: &hasher) - } -} - -struct _CollectionView: UIViewRepresentable { - - struct ViewBox: Hashable { - - static func == (lhs: ViewBox, rhs: ViewBox) -> Bool { - lhs.value.id == rhs.value.id - } - - let value: _VariadicView.Children.Element - - func hash(into hasher: inout Hasher) { - value.id.hash(into: &hasher) - } - - init(_ value: _VariadicView.Children.Element) { - self.value = value - } - - } - - typealias View = DynamicListView - - private let children: _VariadicView.Children - - init(children: _VariadicView.Children) { - self.children = children - } - - func makeUIView(context: Context) -> View { - - let view = View(scrollDirection: .vertical) - - view.registerCell(VersatileCell.self) - view.setUp( - cellProvider: { context in - - let cell = context.dequeueReusableCell(VersatileCell.self) - - if #available(iOS 16, *) { - - cell.contentConfiguration = UIHostingConfiguration(content: { - context.data.id(context.data.id) - }) - .margins(.all, 0) - - } else { - cell.contentConfiguration = HostingConfiguration(context.data) - } - - return cell - - } - ) - return view - } - - func updateUIView(_ uiView: View, context: Context) { - uiView.setContents(Array(children), inSection: 0) - } -} - -#endif