Skip to content

The timing of the Haptic Feedback triggered by CupertinoPicker is incorrect. #169606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
MrHeer opened this issue May 28, 2025 · 2 comments
Open
Labels
a: fidelity Matching the OEM platforms better f: cupertino flutter/packages/flutter/cupertino repository found in release: 3.32 Found to occur in 3.32 found in release: 3.33 Found to occur in 3.33 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on team-design Owned by Design Languages team

Comments

@MrHeer
Copy link

MrHeer commented May 28, 2025

Steps to reproduce

Scroll CupertinoPicker and feel the Haptic Feedback on iPhone.

Expected results

Haptic Feedback should be triggered at the position where an item is selected.

BTW, the onSelectedItemChanged should get the value only when the scrolling settles, use a NotificationListener, listen for ScrollEndNotification and read its FixedExtentMetrics.(ref #92644)

Actual results

Haptic Feedback will be triggered at the mid-position between two items.

Code sample

Code sample
import 'package:flutter/cupertino.dart';

/// Flutter code sample for [CupertinoPicker].

const double _kItemExtent = 32.0;
const List<String> _fruitNames = <String>[
  'Apple',
  'Mango',
  'Banana',
  'Orange',
  'Pineapple',
  'Strawberry',
];

void main() => runApp(const CupertinoPickerApp());

class CupertinoPickerApp extends StatelessWidget {
  const CupertinoPickerApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      theme: CupertinoThemeData(brightness: Brightness.light),
      home: CupertinoPickerExample(),
    );
  }
}

class CupertinoPickerExample extends StatefulWidget {
  const CupertinoPickerExample({super.key});

  @override
  State<CupertinoPickerExample> createState() => _CupertinoPickerExampleState();
}

class _CupertinoPickerExampleState extends State<CupertinoPickerExample> {
  int _selectedFruit = 0;

  // This shows a CupertinoModalPopup with a reasonable fixed height which hosts CupertinoPicker.
  void _showDialog(Widget child) {
    showCupertinoModalPopup<void>(
      context: context,
      builder:
          (BuildContext context) => Container(
            height: 216,
            padding: const EdgeInsets.only(top: 6.0),
            // The Bottom margin is provided to align the popup above the system navigation bar.
            margin: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
            // Provide a background color for the popup.
            color: CupertinoColors.systemBackground.resolveFrom(context),
            // Use a SafeArea widget to avoid system overlaps.
            child: SafeArea(top: false, child: child),
          ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('CupertinoPicker Sample')),
      child: DefaultTextStyle(
        style: TextStyle(color: CupertinoColors.label.resolveFrom(context), fontSize: 22.0),
        child: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text('Selected fruit: '),
              CupertinoButton(
                padding: EdgeInsets.zero,
                // Display a CupertinoPicker with list of fruits.
                onPressed:
                    () => _showDialog(
                      CupertinoPicker(
                        magnification: 1.22,
                        squeeze: 1.2,
                        useMagnifier: true,
                        itemExtent: _kItemExtent,
                        // This sets the initial item.
                        scrollController: FixedExtentScrollController(initialItem: _selectedFruit),
                        // This is called when selected item is changed.
                        onSelectedItemChanged: (int selectedItem) {
                          setState(() {
                            _selectedFruit = selectedItem;
                          });
                        },
                        children: List<Widget>.generate(_fruitNames.length, (int index) {
                          return Center(child: Text(_fruitNames[index]));
                        }),
                      ),
                    ),
                // This displays the selected fruit name.
                child: Text(_fruitNames[_selectedFruit], style: const TextStyle(fontSize: 22.0)),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
@danagbemava-nc danagbemava-nc added the in triage Presently being triaged by the triage team label May 29, 2025
@danagbemava-nc
Copy link
Member

danagbemava-nc commented May 29, 2025

Reproducible using the code sample provided above. As noted above, compared to swiftui, flutter's selection is too fast. In SwiftUI, selection only happens when I let go of the selection unlike flutter which changes the moment a new item sort of comes into focus.

The haptic feedback time in flutter is also too fast as compared to swiftui.

Flutter SwiftUI
ScreenRecording_05-29-2025.09-00-04_1.MP4
ScreenRecording_05-29-2025.09-02-14_1.MP4
swiftui sample
import SwiftUI

struct ContentView: View {
    @State private var selectedFruitIndex: Int = 0
    @State private var showingFruitPicker: Bool = false

    let fruitNames: [String] = [
        "Apple",
        "Mango",
        "Banana",
        "Orange",
        "Pineapple",
        "Strawberry",
    ]

    var body: some View {
        NavigationView {
            VStack {
                Text("Selected fruit: ")
                // Button to trigger the display of the fruit picker
                Button(action: {
                    showingFruitPicker.toggle()
                }) {
                    Text(fruitNames[selectedFruitIndex])
                        .font(.title3)
                        .padding(.vertical, 5)
                        .padding(.horizontal, 10)
                        .background(Capsule().stroke(Color.blue, lineWidth: 1))
                        .foregroundColor(.blue)
                }
            }
            .navigationTitle("Fruit Picker Sample")
            .sheet(isPresented: $showingFruitPicker) {
                // The custom fruit picker view presented as a sheet
                FruitPickerView(selectedFruitIndex: $selectedFruitIndex, fruitNames: fruitNames, showingFruitPicker: $showingFruitPicker)
                    // Customize the presentation detents to mimic CupertinoModalPopup height
                    .presentationDetents([.height(250)])
            }
        }
    }
}

// Custom View for the Fruit Picker, mimicking the CupertinoPicker style
struct FruitPickerView: View {
    @Binding var selectedFruitIndex: Int
    let fruitNames: [String]
    @Binding var showingFruitPicker: Bool

    var body: some View {
        VStack {
            // Optional: A "Done" button to dismiss the picker
            HStack {
                Spacer()
                Button("Done") {
                    showingFruitPicker = false
                }
                .padding()
            }

            // The SwiftUI Picker, styled to look like CupertinoPicker
            Picker("Fruits", selection: $selectedFruitIndex) {
                ForEach(0..<fruitNames.count, id: \.self) { index in
                    Text(fruitNames[index]).tag(index)
                }
            }
            .pickerStyle(.wheel) // This provides the spinning wheel effect
            .labelsHidden() // Hide the default label for a cleaner look
        }
    }
}

// MARK: - Preview
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
flutter doctor -v
[✓] Flutter (Channel stable, 3.32.0, on macOS 15.5 24F74 darwin-arm64, locale en-US) [5.4s]
    • Flutter version 3.32.0 on channel stable at /Users/deanli/dev/stable
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision be698c48a6 (10 days ago), 2025-05-19 12:59:14 -0700
    • Engine revision 1881800949
    • Dart version 3.8.0
    • DevTools version 2.45.1
[!] Flutter (Channel master, 3.33.0-1.0.pre.256, on macOS 15.5 24F74 darwin-arm64, locale en-US) [3.7s]
    • Flutter version 3.33.0-1.0.pre.256 on channel master at /Users/deanli/dev/master
    ! Warning: `flutter` on your path resolves to /Users/deanli/dev/stable/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/deanli/dev/master. Consider adding /Users/deanli/dev/master/bin to the front of your path.
    ! Warning: `dart` on your path resolves to /Users/deanli/dev/stable/bin/dart, which is not inside your current Flutter SDK checkout at /Users/deanli/dev/master. Consider adding /Users/deanli/dev/master/bin to the front of your path.
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision f2625754b3 (9 hours ago), 2025-05-28 18:52:39 -0500
    • Engine revision f2625754b3
    • Dart version 3.9.0 (build 3.9.0-172.0.dev)
    • DevTools version 2.46.0

@danagbemava-nc danagbemava-nc added a: fidelity Matching the OEM platforms better f: cupertino flutter/packages/flutter/cupertino repository has reproducible steps The issue has been confirmed reproducible and is ready to work on team-design Owned by Design Languages team found in release: 3.32 Found to occur in 3.32 found in release: 3.33 Found to occur in 3.33 framework flutter/packages/flutter repository. See also f: labels. and removed in triage Presently being triaged by the triage team labels May 29, 2025
@MrHeer
Copy link
Author

MrHeer commented May 29, 2025

The timing of HapticFeedback also varies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: fidelity Matching the OEM platforms better f: cupertino flutter/packages/flutter/cupertino repository found in release: 3.32 Found to occur in 3.32 found in release: 3.33 Found to occur in 3.33 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on team-design Owned by Design Languages team
Projects
None yet
Development

No branches or pull requests

2 participants