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

810 Views Asked by At

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()
1

There are 1 best solutions below

1
On

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.