Skip to content

[iOS][Add2app] FlutterView doesn't scrolling in native wrapper #170020

Open
@peixinli

Description

@peixinli

Steps to reproduce

Similar to #169291 but on iOS

Internal: b/421369665

Expected results

The FlutterView should be scrollable. Upon reaching the end of the scrollable content within the, it should start scrolling the native wrapper.

Actual results

Cannot scroll on FlutterView.

Code sample

iOS sample AppDelegate.swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?

  let engines = FlutterEngineGroup(name: "multiple-flutters", project: nil)

  // MARK: - UIApplicationDelegate

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    window = UIWindow(frame: UIScreen.main.bounds)

    let mainViewController = ListViewController()
    let navigationController = UINavigationController(rootViewController: mainViewController)

    navigationController.navigationBar.isTranslucent = false

    if let window = window {
      window.rootViewController = navigationController
      window.makeKeyAndVisible()
    }

    return true
  }
}

ListViewController.swift

private let reuseIdentifier = "CollectionViewCell"

class ListViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
  init() {
    super.init(collectionViewLayout: UICollectionViewFlowLayout())
  }

  required init?(coder: NSCoder) {
    super.init(coder: coder)
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    self.collectionView!.register(
      CollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
  }

  // MARK: UICollectionViewDataSource

  override func numberOfSections(in collectionView: UICollectionView) -> Int {
    return 1
  }

  override func collectionView(
    _ collectionView: UICollectionView, numberOfItemsInSection section: Int
  ) -> Int {
    return 5
  }

  override func collectionView(
    _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath
  ) -> UICollectionViewCell {
    guard
      let cell = collectionView.dequeueReusableCell(
        withReuseIdentifier: reuseIdentifier, for: indexPath) as? CollectionViewCell
    else {
      fatalError("Unable to dequeue CollectionViewCell")
    }
    cell.initializeFlutterEmbedding(parent: self)
    return cell
  }

  // MARK: UICollectionViewDelegateFlowLayout

  public func collectionView(
    _ collectionView: UICollectionView,
    layout collectionViewLayout: UICollectionViewLayout,
    sizeForItemAt indexPath: IndexPath
  ) -> CGSize {
    return CGSize(width: collectionView.bounds.width, height: 300)
  }
}

class CollectionViewCell: UICollectionViewCell {
  @IBOutlet weak var myLabel: UILabel!
  weak var parentViewController: UIViewController? = nil

  var engine: FlutterEngine?
  var flutterViewController: FlutterViewController?

  @available(*, unavailable)
  required init?(coder aDecoder: NSCoder) {
    preconditionFailure("init(coder:) has not been implemented")
  }

  public override init(frame: CGRect) {
    super.init(frame: frame)
  }

  public func initializeFlutterEmbedding(parent: UIViewController) {
    guard engine == nil else {
      return
    }

    let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
    engine = appDelegate.engines.makeEngine(with: nil)
    flutterViewController = FlutterViewController(engine: engine!, nibName: nil, bundle: nil)

    guard let flutterViewController, let engine else {
      fatalError("Failed to initialize Gen UI")
    }

    let contentView = self.contentView
    parent.addChild(flutterViewController)
    contentView.addSubview(flutterViewController.view)
    flutterViewController.view.frame = contentView.bounds
    flutterViewController.didMove(toParent: parent)
  }
}

Flutter app
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) =>
      MaterialApp(home: Scaffold(body: CardList()));
}

class CardList extends StatelessWidget {
  final List<int> numbers = List.generate(31, (index) => index + 1);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      padding: EdgeInsets.all(16.0),
      itemCount: numbers.length,
      itemBuilder: (BuildContext context, int index) {
        final number = numbers[index];

        return Padding(
          padding: const EdgeInsets.symmetric(vertical: 8.0),
          child: Card(
            elevation: 4.0,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(15.0),
            ),
            child: Container(
              height: 100,
              width: double.infinity,
              alignment: Alignment.center,
              child: Text(
                '$number',
                style: TextStyle(
                  fontSize: 32,
                  fontWeight: FontWeight.bold,
                  color: Colors.black87,
                ),
              ),
            ),
          ),
        );
      },
    );
  }
}

Screenshots or Video

<details open>

Screenshots / Video demonstration
Simulator.Screen.Recording.-.iPhone.SE.3rd.generation.-.2025-06-04.at.13.22.26.mp4

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work lista: existing-appsIntegration with existing apps via the add-to-app flowcustomer: thoughts (g3)found in release: 3.32Found to occur in 3.32found in release: 3.33Found to occur in 3.33fyi-frameworkFor the attention of Framework teamhas reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-iosiOS applications specificallyteam-iosOwned by iOS platform teamtriaged-iosTriaged by iOS platform team

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions