UIHostingController view's transform gets out of sync when superview is translated before viewDidAppear
Asked Answered
F

1

6

Given: UIHostingController hosted inside UIKit superview.

When: Superview transform modified before viewDidAppear. In this example moved 200pts down.

Then: Subsequent changes to superview transform will be out of sync. In this example I moved it back to its original location. Although the SwiftUI content appears to be in the correct location, hit registration is still at the orginal location, so in order to tap, I need to tap below the area it is being displayed. (see screenshot)

Xcode 12.3 - iPhone 8 Sim (iOS 14)

struct ContentView: View {
    var body: some View {
        Button(action: { print("Testing 123") }, label: {
            Text("Button")
        })
        .padding()
        .background(Color.gray)
    }
}

class RootVC: UIViewController {
    let hostingVC = UIHostingController(rootView: ContentView())
    
    override func viewDidLoad() {
        super.viewDidLoad()
        hostingVC.view.translatesAutoresizingMaskIntoConstraints = false
        view.translatesAutoresizingMaskIntoConstraints = false
        
        addChild(hostingVC)
        view.addSubview(hostingVC.view)
        hostingVC.didMove(toParent: self)
        
        hostingVC.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        hostingVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        hostingVC.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        hostingVC.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true

        // Start translated down before view appears
        view.transform = .init(translationX: 0, y: 200)
    }
        
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // Move up on-appear (this would ususally happen in an animation block)
        view.transform = .init(translationX: 0, y: 0)
    }
}

This is a huge deal-breaker for a component-library I've been building that needs to interact with UIKit-based containers (eg: bottom sheets).

UPDATE:

I've reached out to Apple about this via their Feedback Assistant, but no response yet. A coworker of mine found a workaround that seems to fix the issue, but we have no idea why it works:

// Perform translation
containerView.transform = newTransform
swiftUISubview.layer.position.y = currentTransform.ty
containerView.setNeedsLayout()
containerView.layoutIfNeeded()
Fillin answered 10/2, 2021 at 0:30 Comment(2)
Did you ever find a solution to this?Beastings
@RyanDaulton I added a workaround to the original question.Fillin
E
0

That's absolutely amazing finding! I've also struggled with the similar issue happening while performing custom transitions between UIViewController and UIHostingController. In order to fix this I've used old style frame manipulations instead of CGAffineTransform. For example if we need to move the someView vertically we can do:

private var storedOffset: CGFloat = 0

func apply(offset: CGFloat) {
        someView.center.y += offset - storedOffset
        storedOffset = offset
}

Instead of someView.transform = .init(translationX: 0, y: offset) It works with UIHostingController just fine.

Effy answered 30/8, 2023 at 11:51 Comment(1)
This does not really answer the question. If you have a different question, you can ask it by clicking Ask Question. To get notified when this question gets new answers, you can follow this question. Once you have enough reputation, you can also add a bounty to draw more attention to this question. - From ReviewSurrender

© 2022 - 2024 — McMap. All rights reserved.