Specific problems

Both iOS and flutter have gesture processing. However, there may be some situations where there is a clash of gestures. For example, iOS has a drawer gesture while Flutter has a horizontal swipe gesture.

  • IOS drawer Gesture

1. Requirements scenario

The green part is the controller that holds the flutter (ContentViewController). When swiping the flutter on the left side of the screen, it will draw the iOS Drawer Controller (LeftTableViewController). This drawer gesture is also controlled by iOS. There are a lot of code for iOS drawer gestures on the web. Here is the code taken out of the project. I will not repeat the code address

2. The Flutter page

Suppose that our Flutter page has a horizontal sliding view that needs to be integrated into iOS, as shown below:

The main code of the Flutter page:

Column(children: <Widget>[
          Container(
              child: ListView.separated(
                itemCount: 10,
                shrinkWrap: true,
                scrollDirection: Axis.horizontal,
                separatorBuilder: (BuildContext context, int index) {
                  return Container(
                    width: 5,
                  );
                },
                itemBuilder: (BuildContext context, int index) {
                  return GestureDetector(
                      child: Container(
                    margin: EdgeInsets.all(5),
                    child: new Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text('Test title :${index}',
                            style: TextStyle(fontSize: 19, color: Colors.white),
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis),
                        Container(
                          width: 22,
                          height: 2,
                          color: Colors.white,
                        ),
                        Text(
                          ${index} ${index},
                          textAlign: TextAlign.center,
                          style: TextStyle(fontSize: 13, color: Colors.white),
                        )
                      ],
                    ),
                    width: 180,
                    decoration: BoxDecoration(
                        borderRadius: BorderRadius.all(Radius.circular(10)),
                        color: RandomColor().rColor),
                  ));
                },
              ),
              height: 180),
          Expanded(
              child:Container()
          )
        ]
Copy the code
  • The top of the listView is a horizontal slide. Let’s first specify a requirement that when we slide on the listView, we only trigger the left/right slide effect of the listView. When we slide on the listView, we only trigger the drawer gesture of iOS.

– When I swipe horizontally on the listView with Flutter integrated into iOS, will it trigger the listView swipe or the iOS drawer gesture?

3. Integration effect

We have integrated the Flutter page into the iOS project. There are many ways to integrate the Flutter page online. Here we use the official Google solution

Add code to ContentViewController:

    AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    self.flutterViewController = [[FlutterViewController alloc] initWithEngine:appDelegate.flutterEngine nibName:nil bundle:nil];
    
    self.flutterViewController.view.frame = self.view.bounds;
    [self.view addSubview:self.flutterViewController.view];
Copy the code

Take a look at the results:

As we can see, swiping from left to right on the listView will most likely trigger the drawer gesture on iOS, which is not what we want. We all know why. The iOS gesture takes precedence over the flutter gesture, which triggers the iOS drawer gesture.

4. Solve gesture problems

You know why, and it’s easy to fix. Here is a solution: when our gestures operate on the page of a flutter, the flutter decides on its own whether it needs to trigger the drawer action, so that the thinking on the flutter side is clear. When we swipe on the listView, we do not need iOS. When we have gestures in other regions of flutter, we call the iOS native trigger method.

Process:

We determine if the gesture is on the Flutter page:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { // If flutter is FlutterView (i.e. flutter is clicked), Touch events are not interceptedif ([NSStringFromClass([touch.view class]) isEqualToString:@"FlutterView"] {// When a gesture is on a flutter, NSLog(@) is handled by the flutter"flutterView");
        return NO;
    }

    NSLog(@"native View");
    return  YES;
}
Copy the code

IOS and flutter interact with a channel using the following code:

iOS

    FlutterMethodChannel *scrollMethodChannel = [FlutterMethodChannel methodChannelWithName:@"scrollMethodChannel" binaryMessenger:self.flutterViewController];
    
    self.scrollMethodChannel = scrollMethodChannel;
    
    __weak typeof(self) weakSelf = self;
    [self.scrollMethodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        [weakSelf flutterInvokeNativeMethod:call result:result];
    }];
Copy the code

flutter

  static const MethodChannel _scrollMethodChannel =
      MethodChannel('scrollMethodChannel');

  static String _scrollBeganKey = 'scrollBeganKey';

  static String _scrollUpdateKey = 'scrollUpdateKey';

  static String _scrollEndKey = 'scrollEndKey';
Copy the code

The flutter side only needs to notify iOS of non-listView gestures. ListView gestures do not need to be processed. Then, you just need to deal with the Container inside Expanded.

Column(children: <Widget>[Container(child: listview.separated (...)),// 180), Expanded( child: GestureDetector( onHorizontalDragStart: (detail) { Map<String, dynamic> resInfo = {"offsetX": detail.globalPosition.dx,
                "velocityX": detail.globalPosition.dx
              };

              _scrollMethodChannel.invokeMethod(_scrollBeganKey, resInfo);
            },
            onHorizontalDragEnd: (detail) {
              Map<String, dynamic> resInfo = {
                "offsetX": 0."velocityX": detail.primaryVelocity
              };

              _scrollMethodChannel.invokeMethod(_scrollEndKey, resInfo);
            },
            onHorizontalDragUpdate: (detail) {
              Map<String, dynamic> resInfo = {
                "offsetX": detail.globalPosition.dx,
                "velocityX": detail.primaryDelta
              };

              _scrollMethodChannel.invokeMethod(_scrollUpdateKey, resInfo);
            },
            child: Container(color: Colors.yellow),
          ))
        ]
Copy the code

In fact, it is relatively simple to look at the code. The onHorizontalDragxxx method of GestureDetector is used to monitor the actions of the start of slide, slide ING and slide end, and the hard-mouthing coordinates and slide speed information are transmitted to iOS. After iOS gets the data, it can move the view.

The processing mode of iOS after getting the data:

// Define block: @property (nonatomic, copy) void(^scrollGestureBlock)(CGFloat offsetX,CGFloat velocityX,TYSideState state); - (void)flutterInvokeNativeMethod:(FlutterMethodCall * _Nonnull )call result:(FlutterResult _Nonnull )result{if(! call.arguments)return;
    
    NSLog(@"Test % @",call.arguments);
    CGFloat offsetX = [call.arguments[@"offsetX"] floatValue];
    CGFloat velocityX = [call.arguments[@"velocityX"] floatValue]; /// start slidingif ([call.method isEqualToString:@"scrollBeganKey"]) {if(self.scrollGestureBlock){ dispatch_async(dispatch_get_main_queue(), ^{ self.scrollGestureBlock(0, velocityX, TYSideStateBegan); }); }} /// Slide updateif ([call.method isEqualToString:@"scrollUpdateKey"]) {if(self.scrollGestureBlock){ dispatch_async(dispatch_get_main_queue(), ^{ self.scrollGestureBlock(offsetX, velocityX, TYSideStateUpdate); }); }} /// End the slideif ([call.method isEqualToString:@"scrollEndKey"]) {if(self.scrollGestureBlock){ dispatch_async(dispatch_get_main_queue(), ^{ self.scrollGestureBlock(0, velocityX, TYSideStateEnded); }); }}}Copy the code

I am very happy to start to see the effect, the result is still wrong, carefully look at the picture, when triggered the drawer sliding, the edge has obvious shake.

5, jitter processing

After we sent the sliding message to iOS, iOS would modify the X coordinate of flutterView, for example, from 0 to 10. However, the flutter gesture was not interrupted and the offset was calculated from 0. However, iOS changed the x coordinate and the offset would be 10 error. At this point, it was thought that iOS would save x after modifying x. Add this offset to the next flutter message.

Save x offset:

    if ([self.rootViewController isKindOfClass:[ContentViewController class]]){
        ContentViewController *vc = (ContentViewController *)self.rootViewController;

        vc.currentViewOffsetX = xoffset;
    }
Copy the code

Use x offset:

/// slide to updateif ([call.method isEqualToString:@"scrollUpdateKey"]) {if(self.scrollGestureBlock){ dispatch_async(dispatch_get_main_queue(), ^{ self.scrollGestureBlock(offsetX + self.currentViewOffsetX, velocityX, TYSideStateUpdate); }); }} /// End the slideif ([call.method isEqualToString:@"scrollEndKey"]) {if(self.scrollGestureBlock){ dispatch_async(dispatch_get_main_queue(), ^{ self.scrollGestureBlock(self.currentViewOffsetX, velocityX, TYSideStateEnded); }); }}Copy the code

Effect:

As you can see, the ListView slides left and right normally when the flutter is on top of the ListView without touching iOS. The iOS drawer gesture is triggered when the flutter is on the non-Listview area underneath the ListView without shaking.

The demo store making address: https://github.com/TonyReet/iOS-flutter-gesture-conflict