Use SwiftUI with UIKit

RSS for tag

Discuss the WWDC22 Session Use SwiftUI with UIKit

Posts under wwdc2022-10072 tag

14 Posts

Post

Replies

Boosts

Views

Activity

UICollectionView How to make a cell size itself dynamically based on its UIHostingConfiguration?
I have made an UICollectionView in which you can double tap a cell to resize it. I'm using a CompositionalLayout, a DiffableDataSource and the new UIHostingConfiguration hosting a SwiftUI View which depends on an ObservableObject. The resizing is triggered by updating the height property of the ObservableObject. That causes the SwiftUI View to change its frame which leads to the collectionView automatically resizing the cell. The caveat is that it does so immediately without animation only jumping between the old and the new frame of the view. The ideal end-goal would be to be able to add a .animation() modifier to the SwiftUI View that then determines animation for both view and cell. Doing so now without additional setup makes the SwiftUI View animate but not the cell. Is there a way to make the cell (orange) follow the size of the view (green) dynamically? The proper way to manipulate the cell animation (as far as I known) is to override initialLayoutAttributesForAppearingItem() and finalLayoutAttributesForDisappearingItem() but since the cell just changes and doesn't appear/disappear they don't have an effect. One could also think of Auto Layout constraints to archive this but I don’t think they are usable with UIHostingConfiguration? I've also tried: subclassing UICollectionViewCell and overriding apply(_ layoutAttributes: UICollectionViewLayoutAttributes) but it only effects the orange cell-background on initial appearance. to put layout.invalidateLayout() or collectionView.layoutIfNeeded() inside UIView.animate() but it does not seem to have an effect on the size change. Any thoughts, hints, ideas are greatly appreciated ✌️ Cheers! Here is the code I used for the first gif: struct CellContentModel { var height: CGFloat? = 100 } class CellContentController: ObservableObject, Identifiable { let id = UUID() @Published var cellContentModel: CellContentModel init(cellContentModel: CellContentModel) { self.cellContentModel = cellContentModel } } class DataStore { var data: [CellContentController] var dataById: [CellContentController.ID: CellContentController] init(data: [CellContentController]) { self.data = data self.dataById = Dictionary(uniqueKeysWithValues: data.map { ($0.id, $0) } ) } static let testData = [ CellContentController(cellContentModel: CellContentModel()), CellContentController(cellContentModel: CellContentModel(height: 80)), CellContentController(cellContentModel: CellContentModel()) ] } class CollectionViewController: UIViewController { enum Section { case first } var dataStore = DataStore(data: DataStore.testData) private var layout: UICollectionViewCompositionalLayout! private var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource<Section, CellContentController.ID>! override func loadView() { createLayout() createCollectionView() createDataSource() view = collectionView } } // - MARK: Layout extension CollectionViewController { func createLayout() { let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50)) let Item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.8), heightDimension: .estimated(300)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [Item]) let section = NSCollectionLayoutSection(group: group) layout = .init(section: section) } } // - MARK: CollectionView extension CollectionViewController { func createCollectionView() { collectionView = .init(frame: .zero, collectionViewLayout: layout) let doubleTapGestureRecognizer = DoubleTapGestureRecognizer() doubleTapGestureRecognizer.doubleTapAction = { [unowned self] touch, _ in let touchLocation = touch.location(in: collectionView) guard let touchedIndexPath = collectionView.indexPathForItem(at: touchLocation) else { return } let touchedItemIdentifier = dataSource.itemIdentifier(for: touchedIndexPath)! dataStore.dataById[touchedItemIdentifier]!.cellContentModel.height = dataStore.dataById[touchedItemIdentifier]!.cellContentModel.height == 100 ? nil : 100 } collectionView.addGestureRecognizer(doubleTapGestureRecognizer) } } // - MARK: DataSource extension CollectionViewController { func createDataSource() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewCell, CellContentController.ID>() { cell, indexPath, itemIdentifier in let cellContentController = self.dataStore.dataById[itemIdentifier]! cell.contentConfiguration = UIHostingConfiguration { TextView(cellContentController: cellContentController) } .background(.orange) } dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier) } var initialSnapshot = NSDiffableDataSourceSnapshot<Section, CellContentController.ID>() initialSnapshot.appendSections([Section.first]) initialSnapshot.appendItems(dataStore.data.map{ $0.id }, toSection: Section.first) dataSource.applySnapshotUsingReloadData(initialSnapshot) } } class DoubleTapGestureRecognizer: UITapGestureRecognizer { var doubleTapAction: ((UITouch, UIEvent) -> Void)? override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) { if touches.first!.tapCount == 2 { doubleTapAction?(touches.first!, event) } } } struct TextView: View { @StateObject var cellContentController: CellContentController var body: some View { Text(cellContentController.cellContentModel.height?.description ?? "nil") .frame(height: cellContentController.cellContentModel.height, alignment: .top) .background(.green) } }
2
1
4.6k
Dec ’23
UITouch after SwiftUI Drag
I'm implementing a SwiftUI control over a UIView's drawing surface. Often you are doing both at the same time. Problem is that starting with the SwiftUI gesture will block the UIKit's UITouch(s). And yet, it works when you start with a UITouch first and then a SwiftUI gesture. Also need UITouch's force and majorRadius, which isn't available in a SwiftUI gesture (I think). Here's an example: import SwiftUI struct ContentView: View { @GestureState private var touchXY: CGPoint = .zero var body: some View { ZStack { TouchRepresentable() Rectangle() .foregroundColor(.red) .frame(width: 128, height: 128) .gesture(DragGesture(minimumDistance: 0) .updating($touchXY) { (value, touchXY, _) in touchXY = value.location}) .onChange(of: touchXY) { print(String(format:"SwiftUI(%.2g,%.2g)", $0.x,$0.y), terminator: " ") } //.allowsHitTesting(true) no difference } } } struct TouchRepresentable: UIViewRepresentable { typealias Context = UIViewRepresentableContext<TouchRepresentable> public func makeUIView(context: Context) -> TouchView { return TouchView() } public func updateUIView(_ uiView: TouchView, context: Context) {} } class TouchView: UIView, UIGestureRecognizerDelegate { func updateTouches(_ touches: Set<UITouch>, with event: UIEvent?) { for touch in touches { let location = touch.location(in: nil) print(String(format: "UIKit(%g,%g) ", round(location.x), round(location.y)), terminator: " ") } } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { updateTouches(touches, with: event) } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { updateTouches(touches, with: event) } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { updateTouches(touches, with: event) } override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { updateTouches(touches, with: event) } } Because this is multitouch, you need to test on a real device. Sequence A: finger 1 starts dragging outside of red square (UIKit) finger 2 simultaneously drags inside of red square (SwiftUI) ⟹ Console shows both UIKit and SwiftUI touch positions Sequence B: finger 1 starts dragging inside of red square (SwiftUI) finger 2 simultaneously drags outside of red square (UIKit) ⟹ Console shows only SwiftUI touch positions Would like to get both positions in Sequence B.
2
0
2.1k
Aug ’23
Static method 'buildExpression' requires that 'Image' conform to 'View'
import Foundation import CoreData import SwiftUI struct FamilyView: View { @Environment(.managedObjectContext) private var viewContext @StateObject private var familyMembersData = FamilyMembersData(context: PersistenceController.shared.container.viewContext) var body: some View { NavigationView { List(familyMembersData.members) { member in NavigationLink(destination: FamilyMemberDetailView(member: member)) { FamilyMemberView(member: member) } } .navigationBarTitle(Text("Family Members")) .navigationBarItems(trailing: NavigationLink(destination: AddFamilyMemberView()) { Image(systemName: "plus.circle") } ) .navigationViewStyle(StackNavigationViewStyle()) } .onAppear { familyMembersData.fetchFamilyMembers() } } }
1
0
3.2k
Apr ’23
Is embedding a UIHostingController inside a UICollectionViewCell or UITableViewCell considered "misuse"?
With the release of UIHostingConfiguration for iOS 16 and above, I've seen a few different blog posts and libraries that try to back-port this functionality to older iOS versions. All of the solutions I've come across will have a UIHostingController inside of the cell and will sometimes have some kind of attempt to handle the parent/child relationship of the ViewController. Would this approach be recommended or would it be considered a misuse of the UIHostingController? Is something similar happening under the hood with UIHostingConfiguration? Here is one example of an implementation you can find online: https://gist.github.com/kylebrowning/044b837a9a98135a15ccbb34f664e31f
1
0
3.5k
Nov ’22
SwiftUI is crashing when changing the tabs
The app is working fine in iOS 15 and above but crashing on the below OS versions. We've 4 tabs (buttons) to update the UI, not sure where exactly it's crashing. Tried with .id(uuid()) but its not working. Please help me. thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x746e6f436465698e)   frame #0: 0x00000001c8c3b638 AttributeGraphAG::Graph::value_ref(AG::AttributeID, AGSwiftMetadata const*, bool*) + 112   frame #1: 0x00000001c8c4d988 AttributeGraphAGGraphGetValue + 304   frame #2: 0x00000001c8c4d670 AttributeGraphAGGraphGetInputValue + 68   frame #3: 0x00000001a82968b8 SwiftUISwiftUI.ViewCache.children(context: AttributeGraph.AnyRuleContext) -> SwiftUI._IncrementalLayout_Children + 120   frame #4: 0x00000001a829d70c SwiftUIclosure #1 (inout τ_0_0.State) -> Swift.Optional<__C.CGRect> in closure #1 (τ_0_0, SwiftUI._IncrementalLayout_PlacementContext) -> Swift.Optional<__C.CGRect> in closure #1 (__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint> in SwiftUI.IncrementalScrollable.makeTarget(at: Swift.Int, anchor: Swift.Optional<SwiftUI.UnitPoint>) -> Swift.Optional<(__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint>> + 108   frame #5: 0x00000001a829d7b8 SwiftUIreabstraction thunk helper <τ_0_0 where τ_0_0: SwiftUI.IncrementalLayout> from @callee_guaranteed (@inout τ_0_0.SwiftUI.IncrementalLayout.State) -> (@unowned Swift.Optional<__C.CGRect>) to @escaping @callee_guaranteed (@inout τ_0_0.SwiftUI.IncrementalLayout.State) -> (@out Swift.Optional<__C.CGRect>) + 40   frame #6: 0x00000001a82a03dc SwiftUIpartial apply forwarder for closure #1 (Swift.UnsafeMutablePointer<τ_0_0.State>) -> τ_1_1 in SwiftUI._ViewCache.withMutableState<τ_0_0, τ_0_1>(type: τ_1_0.Type, _: (inout τ_1_0) -> τ_1_1) -> τ_1_1 + 28   frame #7: 0x00000001a56de5e4 libswiftCore.dylibSwift.withUnsafeMutablePointer<τ_0_0, τ_0_1>(to: inout τ_0_0, _: (Swift.UnsafeMutablePointer<τ_0_0>) throws -> τ_0_1) throws -> τ_0_1 + 28   frame #8: 0x00000001a82990f8 SwiftUISwiftUI._ViewCache.withMutableState<τ_0_0, τ_0_1>(type: τ_1_0.Type, _: (inout τ_1_0) -> τ_1_1) -> τ_1_1 + 304   frame #9: 0x00000001a829d668 SwiftUIclosure #1 (τ_0_0, SwiftUI._IncrementalLayout_PlacementContext) -> Swift.Optional<__C.CGRect> in closure #1 (__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint> in SwiftUI.IncrementalScrollable.makeTarget(at: Swift.Int, anchor: Swift.Optional<SwiftUI.UnitPoint>) -> Swift.Optional<(__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint>> + 248   frame #10: 0x00000001a829d814 SwiftUIreabstraction thunk helper <τ_0_0 where τ_0_0: SwiftUI.IncrementalLayout> from @callee_guaranteed (@in_guaranteed τ_0_0, @in_guaranteed SwiftUI._IncrementalLayout_PlacementContext) -> (@unowned Swift.Optional<__C.CGRect>) to @escaping @callee_guaranteed (@in_guaranteed τ_0_0, @in_guaranteed SwiftUI._IncrementalLayout_PlacementContext) -> (@out Swift.Optional<__C.CGRect>) + 40   frame #11: 0x00000001a8299378 SwiftUISwiftUI._ViewCache.withPlacementData<τ_0_0>((τ_0_0, SwiftUI._IncrementalLayout_PlacementContext) -> τ_1_0) -> τ_1_0 + 292   frame #12: 0x00000001a829d2d0 SwiftUIclosure #1 (__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint> in SwiftUI.IncrementalScrollable.makeTarget(at: Swift.Int, anchor: Swift.Optional<SwiftUI.UnitPoint>) -> Swift.Optional<(__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint>> + 336   frame #13: 0x00000001a82a3a9c SwiftUIpartial apply forwarder for closure #1 (__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint> in SwiftUI.IncrementalScrollable.makeTarget(at: Swift.Int, anchor: Swift.Optional<SwiftUI.UnitPoint>) -> Swift.Optional<(__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint>> + 56   frame #14: 0x00000001a86f11fc SwiftUISwiftUI.HostingScrollView.updateAnimationTarget((__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint>) -> () + 216   frame #15: 0x00000001a86f15c4 SwiftUISwiftUI.HostingScrollView.bounds.didset : __C.CGRect + 188   frame #16: 0x00000001a86f14e8 SwiftUISwiftUI.HostingScrollView.bounds.setter : __C.CGRect + 128   frame #17: 0x00000001a86f144c SwiftUI@objc SwiftUI.HostingScrollView.bounds.setter : __C.CGRect + 44   frame #18: 0x00000001a4961e4c UIKitCore-[UIScrollView setContentOffset:] + 804   frame #19: 0x00000001a497e0e4 UIKitCore-[UIScrollView _setContentOffset:animated:animationCurve:animationAdjustsForContentOffsetDelta:animation:animationConfigurator:] + 824   frame #20: 0x00000001a86f07e4 SwiftUISwiftUI.HostingScrollView.updateContent(offset: __C.CGPoint, frame: __C.CGRect, safeAreaInsets: SwiftUI.EdgeInsets, layoutDirection: SwiftUI.LayoutDirection, mode: SwiftUI.SystemScrollViewState.ContentOffsetMode, isEnabled: Swift.Bool) -> () + 528   frame #21: 0x00000001a849ba1c SwiftUISwiftUI.UpdatedHostingScrollView.updateValue() -> () + 544   frame #22: 0x00000001a8278498 SwiftUIpartial apply forwarder for generic specialization <SwiftUI.HostingScrollView, SwiftUI.UpdatedHostingScrollView> of implicit closure #2 (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in implicit closure #1 (τ_1_0.Type) -> (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in closure #1 () -> (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in closure #1 (Swift.UnsafePointer<τ_1_0>) -> AttributeGraph.Attribute<τ_0_0> in AttributeGraph.Attribute.init<τ_0_0 where τ_0_0 == τ_1_0.Value, τ_1_0: AttributeGraph.StatefulRule>(τ_1_0) -> AttributeGraph.Attribute<τ_0_0> + 24   frame #23: 0x00000001c8c36194 AttributeGraphAG::Graph::UpdateStack::update() + 492   frame #24: 0x00000001c8c365c8 AttributeGraphAG::Graph::update_attribute(AG::data::ptr<AG::Node>, bool) + 332   frame #25: 0x00000001c8c3b664 AttributeGraphAG::Graph::value_ref(AG::AttributeID, AGSwiftMetadata const*, bool*) + 156   frame #26: 0x00000001c8c4d988 AttributeGraphAGGraphGetValue + 304   frame #27: 0x00000001a83fec58 SwiftUISwiftUI.ViewGraph.displayList() -> (SwiftUI.DisplayList, SwiftUI.DisplayList.Version) + 60   frame #28: 0x00000001a88472e4 SwiftUIclosure #1 () -> () in SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> () + 2108   frame #29: 0x00000001a8846274 SwiftUISwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> () + 336   frame #30: 0x00000001a89b86e0 SwiftUISwiftUI._UIHostingView.displayLinkTimer(timestamp: SwiftUI.Time) -> () + 172   frame #37: 0x00000001a1b24440 CoreFoundation__CFRunLoopDoSource1 + 596   frame #38: 0x00000001a1b1e320 CoreFoundation__CFRunLoopRun + 2360   frame #39: 0x00000001a1b1d4bc CoreFoundationCFRunLoopRunSpecific + 600   frame #40: 0x00000001b85a2820 GraphicsServicesGSEventRunModal + 164   frame #41: 0x00000001a44c1734 UIKitCore-[UIApplication _run] + 1072   frame #42: 0x00000001a44c6e10 UIKitCoreUIApplicationMain + 168  * frame #43: 0x0000000100c71e64 FiosTVmain at AppDelegate.swift:17:7   frame #44: 0x00000001a17e4e60 libdyld.dylib`start + 4
1
0
849
Oct ’22
@FocusState doesn't work with UIHostingConfiguration
Hi! I'm experimenting with UIHostingConfiguration. I put a textfield in the cell's coniguration and would like to observe its state. I created a @FocusState variable and set the focused(_:). But I've noticed that @FocusState doesn't work in UIHostingConfiguration. Here' my code: struct TaskView: View {          @ObservedObject var task: Task     @FocusState private var isFocused: Bool     var body: some View {         HStack(spacing: 10) {             Image(systemName: (task.isCompleted == true) ? "checkmark.square.fill" : "square")                 .font(Font.system(size: 20, weight: .medium))                 .foregroundColor(.gray)                 .onTapGesture {                     task.isCompleted = true                 }             VStack(alignment: .leading, spacing: 3) {                 TextField("Task", text: $task.name)                     .onSubmit {                         try! (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext.save()                     }                     .focused($isFocused)                     .submitLabel(.done)                     .onChange(of: isFocused) { newValue in                         print(newValue) //always returns false                     }                 HStack(spacing: 2) {                     Image(systemName: "moon.fill")                         .resizable()                         .frame(width: 10, height: 10)                         .foregroundColor(.blue)                     Text("Tomorrow")                         .font(.footnote).bold()                         .foregroundColor(.gray)                 }             }         }     } }
2
0
2.3k
Oct ’22
Workaround for iOS 16 Autorotation bugs
iOS 16 has serious bugs in UIKit without any known workarounds, so much that I had to remove my app from sale. Obviously I am affected and am desperate to know if anyone has found any workaround or if UIKit Engineers can tell me any workarounds. To summarise, the issue is calling setNeedsUpdateOfSupportedInterfaceOrientations shortly (within 0.5 seconds) after app launch doesn't trigger autorotation. It does trigger autorotation when the device is launched from the debugger but not when the app is launched directly. Maybe the debugger incurs some delay that is perhaps sufficient to trigger autorotation? I have filed FB11516363 but need a workaround desperately so as to get my app back on AppStore.
0
0
1.8k
Sep ’22
How does CoreText's CTFontManager deal with variable UIFont?
Suppose I make a UIFont like this: let variations = myCustomVariationsDictionary     let key = kCTFontVariationAttribute as UIFontDescriptor.AttributeName     let uiFontDescriptor = UIFontDescriptor(fontAttributes: [.name: Self.name, key: variations])     let updatedUIFont = UIFont(descriptor: uiFontDescriptor, size: uiFont.pointSize) self.uiFont = updatedUIFont —thereby creating a variable font in RAM and saving it to some object. How do I then register that created font in the CTFontManager such that we can access it by name from SwiftUI? The variation name doesn't correspond with any font file on disk; it will be something crazy like "RobotoFlex_wght0BADF00D_YXCD8282384729" (or whatever) and will be different based the values used for each of the variation axes. The problem I'm facing is that you have to specify the font size when you create a given variable font, or UIFont instance. Now, that font is baked in at that particular size, which might not be the appropriate optical size for the final size it might scale to as a result of DynamicType (e.g. as with an @ScaledMetric for the size). But we can't have the wrapper around the CTFont methods use @ScaledFont property wrapper, since this wrapper does nothing unless used on a conformance to View that's actively being used in an actual SwiftUI graph. In other words, it seems like we have to dynamically recreate the font every single time we want to use it in a Text view, which causes all kinds of nasty memory leaks and unbounded growth of RAM not to mention horrible performance. I've looked at various options available in github—there's exactly one SwiftUI wrapper around dynamic fonts, and it's very uninspiring. So here I am, asking you lot for any advice. Really, I prolly need to file a DTS ticket, but figured I'd try here first. Thanks!
0
0
1.1k
Aug ’22
Use Toolbar in UIHostingConfiguration
Hello! I'm working with the UIHostingConfiguration and would like to set a Toolbar for a TextField which I placed in the content. I've tried to set ToolbarRole and Toolbar Visibility. Unfortunately, it doesn't work. Here's my example code: Cell: cell.configurationUpdateHandler = { (cell, state) in     cell.contentConfiguration = UIHostingConfiguration {          RowView(item: item)      } } View:     @ObservedObject var item: Movie     var body: some View {         TextField("Title", text: $item.title)             .toolbar(content: {                 ToolbarItem(placement: .keyboard) {                     Button("Confirm", action: {})                 }             })             .toolbar(.visible, in: .automatic)     } }
1
0
3.0k
Jul ’22
How to animate cell size change when using UIHostingConfiguration
I made an UICollectionView that uses a CompositionalLayout, DiffableDataSource, the new UIHostingConfiguration and an ObservableObject. You can resize cells by double tapping them (see gif + example code). The resizing is triggered by updating the ObservableObject. Now I want to set an animation mainly to animate the shrinking as well as the size change of the last cell but I can't seem to find the right way to do so. By default there is no animation when shrinking and on the last cell: While there are many guides on how to create dynamically resizeable cells they all either use changes in Autolayout constraints (which can't be used with UIHostingConfiguration - correct me if I'm wrong) or they add an optional part to the view inside UIHostingConfiguration with "if some condition {MyAdditionalView(); .transition(someTransition)}" instead of changing its frame. I also tried these different ways to manipulate the animation but with no success: setting an animation modifier .animation(): this makes a fading animation appear and the view inside the hosting configuration starts to jump (It seems to be anchored in the centre of the cell). setting a transition modifier .transition(): this has no effect on the animation. subclassing UICompositionalLayout and overriding initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) as well as finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath): this has no effect on the animation. subclassing UICollectionViewCell and overriding apply(_ layoutAttributes: UICollectionViewLayoutAttributes): this only effects the orange cell-background on initial appearance. using snapshot.reloadItems([ItemIdentifier]) or snapshot.reconfigureItems([ItemIdentifier]): they have no effect on the animation and lead to inconsistent behaviour when combined with ObservableObject. setting collectionView.selfSizingInvalidation = .enabledIncludingConstraints: no effect. Setting it to .disabled stops the orange cells from resizing altogether. Is there a way to customize the size change animation of a cell using UIHostingConfiguration and ObservableObject? Code without animation: struct CellContentModel {     var height: CGFloat? = 100 } class CellContentController: ObservableObject, Identifiable {     let id = UUID()     @Published var cellContentModel: CellContentModel     init(cellContentModel: CellContentModel) {         self.cellContentModel = cellContentModel     } } class DataStore {     var data: [CellContentController]     var dataById: [CellContentController.ID: CellContentController]     init(data: [CellContentController]) {         self.data = data         self.dataById = Dictionary(uniqueKeysWithValues: data.map { ($0.id, $0) } )     }     static let testData = [         CellContentController(cellContentModel: CellContentModel()),         CellContentController(cellContentModel: CellContentModel(height: 80)),         CellContentController(cellContentModel: CellContentModel())     ] } class CollectionViewController: UIViewController {     enum Section {         case first     }     var dataStore = DataStore(data: DataStore.testData)     private var layout: UICollectionViewCompositionalLayout!     private var collectionView: UICollectionView!     private var dataSource: UICollectionViewDiffableDataSource<Section, CellContentController.ID>!     override func loadView() {         createLayout()         createCollectionView()         createDataSource()         view = collectionView     } } // - MARK: Layout extension CollectionViewController {     func createLayout() {         let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))         let Item = NSCollectionLayoutItem(layoutSize: itemSize)         let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.8), heightDimension: .estimated(300))         let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [Item])         let section = NSCollectionLayoutSection(group: group)         layout = .init(section: section)     } } // - MARK: CollectionView extension CollectionViewController {     func createCollectionView() {         collectionView = .init(frame: .zero, collectionViewLayout: layout)         let doubleTapGestureRecognizer = DoubleTapGestureRecognizer()         doubleTapGestureRecognizer.doubleTapAction = { [unowned self] touch, _ in             let touchLocation = touch.location(in: collectionView)             guard let touchedIndexPath = collectionView.indexPathForItem(at: touchLocation) else { return }             let touchedItemIdentifier = dataSource.itemIdentifier(for: touchedIndexPath)!             dataStore.dataById[touchedItemIdentifier]!.cellContentModel.height = dataStore.dataById[touchedItemIdentifier]!.cellContentModel.height == 100 ? nil : 100 // <- this triggers the resizing }         collectionView.addGestureRecognizer(doubleTapGestureRecognizer)     } } // - MARK: DataSource extension CollectionViewController {     func createDataSource() {         let cellRegistration = UICollectionView.CellRegistration<C, CellContentController.ID>() { cell, indexPath, itemIdentifier in             let cellContentController = self.dataStore.dataById[itemIdentifier]!             cell.contentConfiguration = UIHostingConfiguration {                 TextView(cellContentController: cellContentController)             }             .background(.orange)         }         dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in             return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)         }         var initialSnapshot = NSDiffableDataSourceSnapshot<Section, CellContentController.ID>()         initialSnapshot.appendSections([Section.first])         initialSnapshot.appendItems(dataStore.data.map{ $0.id }, toSection: Section.first)         dataSource.applySnapshotUsingReloadData(initialSnapshot)     } } class DoubleTapGestureRecognizer: UITapGestureRecognizer {     var doubleTapAction: ((UITouch, UIEvent) -> Void)?     override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {         if touches.first!.tapCount == 2 {             doubleTapAction?(touches.first!, event)         }     } } struct TextView: View {     @StateObject var cellContentController: CellContentController     var body: some View { Text(cellContentController.cellContentModel.height?.description ?? "nil")             .frame(height: cellContentController.cellContentModel.height)             .background(.green)     } }
1
1
2.9k
Jul ’22
UICollectionView How to make a cell size itself dynamically based on its UIHostingConfiguration?
I have made an UICollectionView in which you can double tap a cell to resize it. I'm using a CompositionalLayout, a DiffableDataSource and the new UIHostingConfiguration hosting a SwiftUI View which depends on an ObservableObject. The resizing is triggered by updating the height property of the ObservableObject. That causes the SwiftUI View to change its frame which leads to the collectionView automatically resizing the cell. The caveat is that it does so immediately without animation only jumping between the old and the new frame of the view. The ideal end-goal would be to be able to add a .animation() modifier to the SwiftUI View that then determines animation for both view and cell. Doing so now without additional setup makes the SwiftUI View animate but not the cell. Is there a way to make the cell (orange) follow the size of the view (green) dynamically? The proper way to manipulate the cell animation (as far as I known) is to override initialLayoutAttributesForAppearingItem() and finalLayoutAttributesForDisappearingItem() but since the cell just changes and doesn't appear/disappear they don't have an effect. One could also think of Auto Layout constraints to archive this but I don’t think they are usable with UIHostingConfiguration? I've also tried: subclassing UICollectionViewCell and overriding apply(_ layoutAttributes: UICollectionViewLayoutAttributes) but it only effects the orange cell-background on initial appearance. to put layout.invalidateLayout() or collectionView.layoutIfNeeded() inside UIView.animate() but it does not seem to have an effect on the size change. Any thoughts, hints, ideas are greatly appreciated ✌️ Cheers! Here is the code I used for the first gif: struct CellContentModel { var height: CGFloat? = 100 } class CellContentController: ObservableObject, Identifiable { let id = UUID() @Published var cellContentModel: CellContentModel init(cellContentModel: CellContentModel) { self.cellContentModel = cellContentModel } } class DataStore { var data: [CellContentController] var dataById: [CellContentController.ID: CellContentController] init(data: [CellContentController]) { self.data = data self.dataById = Dictionary(uniqueKeysWithValues: data.map { ($0.id, $0) } ) } static let testData = [ CellContentController(cellContentModel: CellContentModel()), CellContentController(cellContentModel: CellContentModel(height: 80)), CellContentController(cellContentModel: CellContentModel()) ] } class CollectionViewController: UIViewController { enum Section { case first } var dataStore = DataStore(data: DataStore.testData) private var layout: UICollectionViewCompositionalLayout! private var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource<Section, CellContentController.ID>! override func loadView() { createLayout() createCollectionView() createDataSource() view = collectionView } } // - MARK: Layout extension CollectionViewController { func createLayout() { let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50)) let Item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.8), heightDimension: .estimated(300)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [Item]) let section = NSCollectionLayoutSection(group: group) layout = .init(section: section) } } // - MARK: CollectionView extension CollectionViewController { func createCollectionView() { collectionView = .init(frame: .zero, collectionViewLayout: layout) let doubleTapGestureRecognizer = DoubleTapGestureRecognizer() doubleTapGestureRecognizer.doubleTapAction = { [unowned self] touch, _ in let touchLocation = touch.location(in: collectionView) guard let touchedIndexPath = collectionView.indexPathForItem(at: touchLocation) else { return } let touchedItemIdentifier = dataSource.itemIdentifier(for: touchedIndexPath)! dataStore.dataById[touchedItemIdentifier]!.cellContentModel.height = dataStore.dataById[touchedItemIdentifier]!.cellContentModel.height == 100 ? nil : 100 } collectionView.addGestureRecognizer(doubleTapGestureRecognizer) } } // - MARK: DataSource extension CollectionViewController { func createDataSource() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewCell, CellContentController.ID>() { cell, indexPath, itemIdentifier in let cellContentController = self.dataStore.dataById[itemIdentifier]! cell.contentConfiguration = UIHostingConfiguration { TextView(cellContentController: cellContentController) } .background(.orange) } dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier) } var initialSnapshot = NSDiffableDataSourceSnapshot<Section, CellContentController.ID>() initialSnapshot.appendSections([Section.first]) initialSnapshot.appendItems(dataStore.data.map{ $0.id }, toSection: Section.first) dataSource.applySnapshotUsingReloadData(initialSnapshot) } } class DoubleTapGestureRecognizer: UITapGestureRecognizer { var doubleTapAction: ((UITouch, UIEvent) -> Void)? override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) { if touches.first!.tapCount == 2 { doubleTapAction?(touches.first!, event) } } } struct TextView: View { @StateObject var cellContentController: CellContentController var body: some View { Text(cellContentController.cellContentModel.height?.description ?? "nil") .frame(height: cellContentController.cellContentModel.height, alignment: .top) .background(.green) } }
Replies
2
Boosts
1
Views
4.6k
Activity
Dec ’23
UITouch after SwiftUI Drag
I'm implementing a SwiftUI control over a UIView's drawing surface. Often you are doing both at the same time. Problem is that starting with the SwiftUI gesture will block the UIKit's UITouch(s). And yet, it works when you start with a UITouch first and then a SwiftUI gesture. Also need UITouch's force and majorRadius, which isn't available in a SwiftUI gesture (I think). Here's an example: import SwiftUI struct ContentView: View { @GestureState private var touchXY: CGPoint = .zero var body: some View { ZStack { TouchRepresentable() Rectangle() .foregroundColor(.red) .frame(width: 128, height: 128) .gesture(DragGesture(minimumDistance: 0) .updating($touchXY) { (value, touchXY, _) in touchXY = value.location}) .onChange(of: touchXY) { print(String(format:"SwiftUI(%.2g,%.2g)", $0.x,$0.y), terminator: " ") } //.allowsHitTesting(true) no difference } } } struct TouchRepresentable: UIViewRepresentable { typealias Context = UIViewRepresentableContext<TouchRepresentable> public func makeUIView(context: Context) -> TouchView { return TouchView() } public func updateUIView(_ uiView: TouchView, context: Context) {} } class TouchView: UIView, UIGestureRecognizerDelegate { func updateTouches(_ touches: Set<UITouch>, with event: UIEvent?) { for touch in touches { let location = touch.location(in: nil) print(String(format: "UIKit(%g,%g) ", round(location.x), round(location.y)), terminator: " ") } } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { updateTouches(touches, with: event) } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { updateTouches(touches, with: event) } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { updateTouches(touches, with: event) } override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { updateTouches(touches, with: event) } } Because this is multitouch, you need to test on a real device. Sequence A: finger 1 starts dragging outside of red square (UIKit) finger 2 simultaneously drags inside of red square (SwiftUI) ⟹ Console shows both UIKit and SwiftUI touch positions Sequence B: finger 1 starts dragging inside of red square (SwiftUI) finger 2 simultaneously drags outside of red square (UIKit) ⟹ Console shows only SwiftUI touch positions Would like to get both positions in Sequence B.
Replies
2
Boosts
0
Views
2.1k
Activity
Aug ’23
Static method 'buildExpression' requires that 'Image' conform to 'View'
import Foundation import CoreData import SwiftUI struct FamilyView: View { @Environment(.managedObjectContext) private var viewContext @StateObject private var familyMembersData = FamilyMembersData(context: PersistenceController.shared.container.viewContext) var body: some View { NavigationView { List(familyMembersData.members) { member in NavigationLink(destination: FamilyMemberDetailView(member: member)) { FamilyMemberView(member: member) } } .navigationBarTitle(Text("Family Members")) .navigationBarItems(trailing: NavigationLink(destination: AddFamilyMemberView()) { Image(systemName: "plus.circle") } ) .navigationViewStyle(StackNavigationViewStyle()) } .onAppear { familyMembersData.fetchFamilyMembers() } } }
Replies
1
Boosts
0
Views
3.2k
Activity
Apr ’23
Bottom Sheet hide the toolbar bottom icons
I want to apply bottom sheet like an image posted below. But when I applied it, it hide my bottom toolbar items. Using SWIFTUI I did not seen any solution like it. Kindly solve it ASAP.
Replies
4
Boosts
0
Views
2.2k
Activity
Nov ’22
Is embedding a UIHostingController inside a UICollectionViewCell or UITableViewCell considered "misuse"?
With the release of UIHostingConfiguration for iOS 16 and above, I've seen a few different blog posts and libraries that try to back-port this functionality to older iOS versions. All of the solutions I've come across will have a UIHostingController inside of the cell and will sometimes have some kind of attempt to handle the parent/child relationship of the ViewController. Would this approach be recommended or would it be considered a misuse of the UIHostingController? Is something similar happening under the hood with UIHostingConfiguration? Here is one example of an implementation you can find online: https://gist.github.com/kylebrowning/044b837a9a98135a15ccbb34f664e31f
Replies
1
Boosts
0
Views
3.5k
Activity
Nov ’22
SwiftUI is crashing when changing the tabs
The app is working fine in iOS 15 and above but crashing on the below OS versions. We've 4 tabs (buttons) to update the UI, not sure where exactly it's crashing. Tried with .id(uuid()) but its not working. Please help me. thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x746e6f436465698e)   frame #0: 0x00000001c8c3b638 AttributeGraphAG::Graph::value_ref(AG::AttributeID, AGSwiftMetadata const*, bool*) + 112   frame #1: 0x00000001c8c4d988 AttributeGraphAGGraphGetValue + 304   frame #2: 0x00000001c8c4d670 AttributeGraphAGGraphGetInputValue + 68   frame #3: 0x00000001a82968b8 SwiftUISwiftUI.ViewCache.children(context: AttributeGraph.AnyRuleContext) -> SwiftUI._IncrementalLayout_Children + 120   frame #4: 0x00000001a829d70c SwiftUIclosure #1 (inout τ_0_0.State) -> Swift.Optional<__C.CGRect> in closure #1 (τ_0_0, SwiftUI._IncrementalLayout_PlacementContext) -> Swift.Optional<__C.CGRect> in closure #1 (__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint> in SwiftUI.IncrementalScrollable.makeTarget(at: Swift.Int, anchor: Swift.Optional<SwiftUI.UnitPoint>) -> Swift.Optional<(__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint>> + 108   frame #5: 0x00000001a829d7b8 SwiftUIreabstraction thunk helper <τ_0_0 where τ_0_0: SwiftUI.IncrementalLayout> from @callee_guaranteed (@inout τ_0_0.SwiftUI.IncrementalLayout.State) -> (@unowned Swift.Optional<__C.CGRect>) to @escaping @callee_guaranteed (@inout τ_0_0.SwiftUI.IncrementalLayout.State) -> (@out Swift.Optional<__C.CGRect>) + 40   frame #6: 0x00000001a82a03dc SwiftUIpartial apply forwarder for closure #1 (Swift.UnsafeMutablePointer<τ_0_0.State>) -> τ_1_1 in SwiftUI._ViewCache.withMutableState<τ_0_0, τ_0_1>(type: τ_1_0.Type, _: (inout τ_1_0) -> τ_1_1) -> τ_1_1 + 28   frame #7: 0x00000001a56de5e4 libswiftCore.dylibSwift.withUnsafeMutablePointer<τ_0_0, τ_0_1>(to: inout τ_0_0, _: (Swift.UnsafeMutablePointer<τ_0_0>) throws -> τ_0_1) throws -> τ_0_1 + 28   frame #8: 0x00000001a82990f8 SwiftUISwiftUI._ViewCache.withMutableState<τ_0_0, τ_0_1>(type: τ_1_0.Type, _: (inout τ_1_0) -> τ_1_1) -> τ_1_1 + 304   frame #9: 0x00000001a829d668 SwiftUIclosure #1 (τ_0_0, SwiftUI._IncrementalLayout_PlacementContext) -> Swift.Optional<__C.CGRect> in closure #1 (__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint> in SwiftUI.IncrementalScrollable.makeTarget(at: Swift.Int, anchor: Swift.Optional<SwiftUI.UnitPoint>) -> Swift.Optional<(__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint>> + 248   frame #10: 0x00000001a829d814 SwiftUIreabstraction thunk helper <τ_0_0 where τ_0_0: SwiftUI.IncrementalLayout> from @callee_guaranteed (@in_guaranteed τ_0_0, @in_guaranteed SwiftUI._IncrementalLayout_PlacementContext) -> (@unowned Swift.Optional<__C.CGRect>) to @escaping @callee_guaranteed (@in_guaranteed τ_0_0, @in_guaranteed SwiftUI._IncrementalLayout_PlacementContext) -> (@out Swift.Optional<__C.CGRect>) + 40   frame #11: 0x00000001a8299378 SwiftUISwiftUI._ViewCache.withPlacementData<τ_0_0>((τ_0_0, SwiftUI._IncrementalLayout_PlacementContext) -> τ_1_0) -> τ_1_0 + 292   frame #12: 0x00000001a829d2d0 SwiftUIclosure #1 (__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint> in SwiftUI.IncrementalScrollable.makeTarget(at: Swift.Int, anchor: Swift.Optional<SwiftUI.UnitPoint>) -> Swift.Optional<(__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint>> + 336   frame #13: 0x00000001a82a3a9c SwiftUIpartial apply forwarder for closure #1 (__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint> in SwiftUI.IncrementalScrollable.makeTarget(at: Swift.Int, anchor: Swift.Optional<SwiftUI.UnitPoint>) -> Swift.Optional<(__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint>> + 56   frame #14: 0x00000001a86f11fc SwiftUISwiftUI.HostingScrollView.updateAnimationTarget((__C.CGSize, __C.CGRect) -> Swift.Optional<__C.CGPoint>) -> () + 216   frame #15: 0x00000001a86f15c4 SwiftUISwiftUI.HostingScrollView.bounds.didset : __C.CGRect + 188   frame #16: 0x00000001a86f14e8 SwiftUISwiftUI.HostingScrollView.bounds.setter : __C.CGRect + 128   frame #17: 0x00000001a86f144c SwiftUI@objc SwiftUI.HostingScrollView.bounds.setter : __C.CGRect + 44   frame #18: 0x00000001a4961e4c UIKitCore-[UIScrollView setContentOffset:] + 804   frame #19: 0x00000001a497e0e4 UIKitCore-[UIScrollView _setContentOffset:animated:animationCurve:animationAdjustsForContentOffsetDelta:animation:animationConfigurator:] + 824   frame #20: 0x00000001a86f07e4 SwiftUISwiftUI.HostingScrollView.updateContent(offset: __C.CGPoint, frame: __C.CGRect, safeAreaInsets: SwiftUI.EdgeInsets, layoutDirection: SwiftUI.LayoutDirection, mode: SwiftUI.SystemScrollViewState.ContentOffsetMode, isEnabled: Swift.Bool) -> () + 528   frame #21: 0x00000001a849ba1c SwiftUISwiftUI.UpdatedHostingScrollView.updateValue() -> () + 544   frame #22: 0x00000001a8278498 SwiftUIpartial apply forwarder for generic specialization <SwiftUI.HostingScrollView, SwiftUI.UpdatedHostingScrollView> of implicit closure #2 (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in implicit closure #1 (τ_1_0.Type) -> (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in closure #1 () -> (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in closure #1 (Swift.UnsafePointer<τ_1_0>) -> AttributeGraph.Attribute<τ_0_0> in AttributeGraph.Attribute.init<τ_0_0 where τ_0_0 == τ_1_0.Value, τ_1_0: AttributeGraph.StatefulRule>(τ_1_0) -> AttributeGraph.Attribute<τ_0_0> + 24   frame #23: 0x00000001c8c36194 AttributeGraphAG::Graph::UpdateStack::update() + 492   frame #24: 0x00000001c8c365c8 AttributeGraphAG::Graph::update_attribute(AG::data::ptr<AG::Node>, bool) + 332   frame #25: 0x00000001c8c3b664 AttributeGraphAG::Graph::value_ref(AG::AttributeID, AGSwiftMetadata const*, bool*) + 156   frame #26: 0x00000001c8c4d988 AttributeGraphAGGraphGetValue + 304   frame #27: 0x00000001a83fec58 SwiftUISwiftUI.ViewGraph.displayList() -> (SwiftUI.DisplayList, SwiftUI.DisplayList.Version) + 60   frame #28: 0x00000001a88472e4 SwiftUIclosure #1 () -> () in SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> () + 2108   frame #29: 0x00000001a8846274 SwiftUISwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> () + 336   frame #30: 0x00000001a89b86e0 SwiftUISwiftUI._UIHostingView.displayLinkTimer(timestamp: SwiftUI.Time) -> () + 172   frame #37: 0x00000001a1b24440 CoreFoundation__CFRunLoopDoSource1 + 596   frame #38: 0x00000001a1b1e320 CoreFoundation__CFRunLoopRun + 2360   frame #39: 0x00000001a1b1d4bc CoreFoundationCFRunLoopRunSpecific + 600   frame #40: 0x00000001b85a2820 GraphicsServicesGSEventRunModal + 164   frame #41: 0x00000001a44c1734 UIKitCore-[UIApplication _run] + 1072   frame #42: 0x00000001a44c6e10 UIKitCoreUIApplicationMain + 168  * frame #43: 0x0000000100c71e64 FiosTVmain at AppDelegate.swift:17:7   frame #44: 0x00000001a17e4e60 libdyld.dylib`start + 4
Replies
1
Boosts
0
Views
849
Activity
Oct ’22
@FocusState doesn't work with UIHostingConfiguration
Hi! I'm experimenting with UIHostingConfiguration. I put a textfield in the cell's coniguration and would like to observe its state. I created a @FocusState variable and set the focused(_:). But I've noticed that @FocusState doesn't work in UIHostingConfiguration. Here' my code: struct TaskView: View {          @ObservedObject var task: Task     @FocusState private var isFocused: Bool     var body: some View {         HStack(spacing: 10) {             Image(systemName: (task.isCompleted == true) ? "checkmark.square.fill" : "square")                 .font(Font.system(size: 20, weight: .medium))                 .foregroundColor(.gray)                 .onTapGesture {                     task.isCompleted = true                 }             VStack(alignment: .leading, spacing: 3) {                 TextField("Task", text: $task.name)                     .onSubmit {                         try! (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext.save()                     }                     .focused($isFocused)                     .submitLabel(.done)                     .onChange(of: isFocused) { newValue in                         print(newValue) //always returns false                     }                 HStack(spacing: 2) {                     Image(systemName: "moon.fill")                         .resizable()                         .frame(width: 10, height: 10)                         .foregroundColor(.blue)                     Text("Tomorrow")                         .font(.footnote).bold()                         .foregroundColor(.gray)                 }             }         }     } }
Replies
2
Boosts
0
Views
2.3k
Activity
Oct ’22
Workaround for iOS 16 Autorotation bugs
iOS 16 has serious bugs in UIKit without any known workarounds, so much that I had to remove my app from sale. Obviously I am affected and am desperate to know if anyone has found any workaround or if UIKit Engineers can tell me any workarounds. To summarise, the issue is calling setNeedsUpdateOfSupportedInterfaceOrientations shortly (within 0.5 seconds) after app launch doesn't trigger autorotation. It does trigger autorotation when the device is launched from the debugger but not when the app is launched directly. Maybe the debugger incurs some delay that is perhaps sufficient to trigger autorotation? I have filed FB11516363 but need a workaround desperately so as to get my app back on AppStore.
Replies
0
Boosts
0
Views
1.8k
Activity
Sep ’22
How does CoreText's CTFontManager deal with variable UIFont?
Suppose I make a UIFont like this: let variations = myCustomVariationsDictionary     let key = kCTFontVariationAttribute as UIFontDescriptor.AttributeName     let uiFontDescriptor = UIFontDescriptor(fontAttributes: [.name: Self.name, key: variations])     let updatedUIFont = UIFont(descriptor: uiFontDescriptor, size: uiFont.pointSize) self.uiFont = updatedUIFont —thereby creating a variable font in RAM and saving it to some object. How do I then register that created font in the CTFontManager such that we can access it by name from SwiftUI? The variation name doesn't correspond with any font file on disk; it will be something crazy like "RobotoFlex_wght0BADF00D_YXCD8282384729" (or whatever) and will be different based the values used for each of the variation axes. The problem I'm facing is that you have to specify the font size when you create a given variable font, or UIFont instance. Now, that font is baked in at that particular size, which might not be the appropriate optical size for the final size it might scale to as a result of DynamicType (e.g. as with an @ScaledMetric for the size). But we can't have the wrapper around the CTFont methods use @ScaledFont property wrapper, since this wrapper does nothing unless used on a conformance to View that's actively being used in an actual SwiftUI graph. In other words, it seems like we have to dynamically recreate the font every single time we want to use it in a Text view, which causes all kinds of nasty memory leaks and unbounded growth of RAM not to mention horrible performance. I've looked at various options available in github—there's exactly one SwiftUI wrapper around dynamic fonts, and it's very uninspiring. So here I am, asking you lot for any advice. Really, I prolly need to file a DTS ticket, but figured I'd try here first. Thanks!
Replies
0
Boosts
0
Views
1.1k
Activity
Aug ’22
iOS 16 customized transition animation
I customized a transition animation from bottom to top. When jumping from landscape vc to portrait vc, the animation will appear portrait, landscape, and portrait.
Replies
0
Boosts
0
Views
867
Activity
Jul ’22
Use Toolbar in UIHostingConfiguration
Hello! I'm working with the UIHostingConfiguration and would like to set a Toolbar for a TextField which I placed in the content. I've tried to set ToolbarRole and Toolbar Visibility. Unfortunately, it doesn't work. Here's my example code: Cell: cell.configurationUpdateHandler = { (cell, state) in     cell.contentConfiguration = UIHostingConfiguration {          RowView(item: item)      } } View:     @ObservedObject var item: Movie     var body: some View {         TextField("Title", text: $item.title)             .toolbar(content: {                 ToolbarItem(placement: .keyboard) {                     Button("Confirm", action: {})                 }             })             .toolbar(.visible, in: .automatic)     } }
Replies
1
Boosts
0
Views
3.0k
Activity
Jul ’22
How to animate cell size change when using UIHostingConfiguration
I made an UICollectionView that uses a CompositionalLayout, DiffableDataSource, the new UIHostingConfiguration and an ObservableObject. You can resize cells by double tapping them (see gif + example code). The resizing is triggered by updating the ObservableObject. Now I want to set an animation mainly to animate the shrinking as well as the size change of the last cell but I can't seem to find the right way to do so. By default there is no animation when shrinking and on the last cell: While there are many guides on how to create dynamically resizeable cells they all either use changes in Autolayout constraints (which can't be used with UIHostingConfiguration - correct me if I'm wrong) or they add an optional part to the view inside UIHostingConfiguration with "if some condition {MyAdditionalView(); .transition(someTransition)}" instead of changing its frame. I also tried these different ways to manipulate the animation but with no success: setting an animation modifier .animation(): this makes a fading animation appear and the view inside the hosting configuration starts to jump (It seems to be anchored in the centre of the cell). setting a transition modifier .transition(): this has no effect on the animation. subclassing UICompositionalLayout and overriding initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) as well as finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath): this has no effect on the animation. subclassing UICollectionViewCell and overriding apply(_ layoutAttributes: UICollectionViewLayoutAttributes): this only effects the orange cell-background on initial appearance. using snapshot.reloadItems([ItemIdentifier]) or snapshot.reconfigureItems([ItemIdentifier]): they have no effect on the animation and lead to inconsistent behaviour when combined with ObservableObject. setting collectionView.selfSizingInvalidation = .enabledIncludingConstraints: no effect. Setting it to .disabled stops the orange cells from resizing altogether. Is there a way to customize the size change animation of a cell using UIHostingConfiguration and ObservableObject? Code without animation: struct CellContentModel {     var height: CGFloat? = 100 } class CellContentController: ObservableObject, Identifiable {     let id = UUID()     @Published var cellContentModel: CellContentModel     init(cellContentModel: CellContentModel) {         self.cellContentModel = cellContentModel     } } class DataStore {     var data: [CellContentController]     var dataById: [CellContentController.ID: CellContentController]     init(data: [CellContentController]) {         self.data = data         self.dataById = Dictionary(uniqueKeysWithValues: data.map { ($0.id, $0) } )     }     static let testData = [         CellContentController(cellContentModel: CellContentModel()),         CellContentController(cellContentModel: CellContentModel(height: 80)),         CellContentController(cellContentModel: CellContentModel())     ] } class CollectionViewController: UIViewController {     enum Section {         case first     }     var dataStore = DataStore(data: DataStore.testData)     private var layout: UICollectionViewCompositionalLayout!     private var collectionView: UICollectionView!     private var dataSource: UICollectionViewDiffableDataSource<Section, CellContentController.ID>!     override func loadView() {         createLayout()         createCollectionView()         createDataSource()         view = collectionView     } } // - MARK: Layout extension CollectionViewController {     func createLayout() {         let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))         let Item = NSCollectionLayoutItem(layoutSize: itemSize)         let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.8), heightDimension: .estimated(300))         let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [Item])         let section = NSCollectionLayoutSection(group: group)         layout = .init(section: section)     } } // - MARK: CollectionView extension CollectionViewController {     func createCollectionView() {         collectionView = .init(frame: .zero, collectionViewLayout: layout)         let doubleTapGestureRecognizer = DoubleTapGestureRecognizer()         doubleTapGestureRecognizer.doubleTapAction = { [unowned self] touch, _ in             let touchLocation = touch.location(in: collectionView)             guard let touchedIndexPath = collectionView.indexPathForItem(at: touchLocation) else { return }             let touchedItemIdentifier = dataSource.itemIdentifier(for: touchedIndexPath)!             dataStore.dataById[touchedItemIdentifier]!.cellContentModel.height = dataStore.dataById[touchedItemIdentifier]!.cellContentModel.height == 100 ? nil : 100 // <- this triggers the resizing }         collectionView.addGestureRecognizer(doubleTapGestureRecognizer)     } } // - MARK: DataSource extension CollectionViewController {     func createDataSource() {         let cellRegistration = UICollectionView.CellRegistration<C, CellContentController.ID>() { cell, indexPath, itemIdentifier in             let cellContentController = self.dataStore.dataById[itemIdentifier]!             cell.contentConfiguration = UIHostingConfiguration {                 TextView(cellContentController: cellContentController)             }             .background(.orange)         }         dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in             return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)         }         var initialSnapshot = NSDiffableDataSourceSnapshot<Section, CellContentController.ID>()         initialSnapshot.appendSections([Section.first])         initialSnapshot.appendItems(dataStore.data.map{ $0.id }, toSection: Section.first)         dataSource.applySnapshotUsingReloadData(initialSnapshot)     } } class DoubleTapGestureRecognizer: UITapGestureRecognizer {     var doubleTapAction: ((UITouch, UIEvent) -> Void)?     override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {         if touches.first!.tapCount == 2 {             doubleTapAction?(touches.first!, event)         }     } } struct TextView: View {     @StateObject var cellContentController: CellContentController     var body: some View { Text(cellContentController.cellContentModel.height?.description ?? "nil")             .frame(height: cellContentController.cellContentModel.height)             .background(.green)     } }
Replies
1
Boosts
1
Views
2.9k
Activity
Jul ’22
@ObservableObject and NSManagedObject
Can I use NSManagedObject with @ObservableObject and @Published to auto-reconfigure my collection view's cells?
Replies
1
Boosts
0
Views
1.8k
Activity
Jun ’22
The video "Use SwiftUI with UIKit" discuss sample code but none is provided.
The video at one point says "Download the sample code for this video to find a complete example." but no example is provided. How can I look at the example code?
Replies
1
Boosts
0
Views
1.4k
Activity
Jun ’22