Learn what Felgo offers to help your business succeed. Start your free evaluation today! Felgo for Your Business

Forums

OverviewFelgo 4 Support (Qt 6) › [Help] (iOS) Number Input with OK button

Viewing 7 posts - 1 through 7 (of 7 total)
  • Author
    Posts
  • #25003

    Alberto

    On QT, by default, using inputMethodHints: Qt.ImhDigitsOnly on iOS will open a number input but the thing, it does not have the OK/Confirm button.

     

    This is very important for my App, and Felgo should have this improvement. I could only find this: https://bugreports.qt.io/browse/QTBUG-66900

    The actual Objective-c code to fix it is this one:

        UIToolbar* numberToolbar = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, 320, 50)];
        numberToolbar.barStyle = UIBarStyleBlackTranslucent;
        numberToolbar.items = @[[[UIBarButtonItem alloc]initWithTitle:@"Cancel" style:UIBarButtonItemStyleBordered target:self action:@selector(cancelNumberPad)],
                             [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
                             [[UIBarButtonItem alloc]initWithTitle:@"Apply" style:UIBarButtonItemStyleDone target:self action:@selector(doneWithNumberPad)]];
        [numberToolbar sizeToFit];
        numberTextField.inputAccessoryView = numberToolbar;

     

    Thing here is I cant find any way to get the ios text input object to apply the inputAccessoryView on Objective-c.

    This is where QT handles all of this: https://code.qt.io/cgit/qt/qtbase.git/tree/src/plugins/platforms/ios/qiosinputcontext.mm?id=64475cc6780515172a5f38428854b14f69da070d&h=6.4.3

     

    Any help is tremendously appreciated.

     

    #25005

    Alex
    Felgo Team

    Hi Alberto,

    I would recommend a QML solution here. There is no native textfield in this case to easily add this accessory view. You could do something like this instead:

    import Felgo
    import QtQuick
    
    App {
      id: app
    
      Navigation {
        id: navigation
    
        NavigationItem {
          iconType: IconType.home
          title: "Test"
    
          NavigationStack {
            AppPage {
              id: page
              title: qsTr("My App")
    
              AppTextField {
                id: numberField
                inputMethodHints: Qt.ImhDigitsOnly
                width: parent.width / 2
                borderColor: focus ? Theme.tintColor : "black"
                borderWidth: 1
                anchors.horizontalCenter: parent.horizontalCenter
                y: dp(25)
              }
            }
          }
        }
      }
    
      // keyboard done helper
      Rectangle {
        id: keyboardHelperPanel
    
        width: parent.width
        height: Qt.inputMethod.keyboardRectangle.height + dp(42)
        anchors.bottom: parent.bottom
        color: Theme.tabBar.backgroundColor
        visible: false
    
        // Wo only show the done controls if keyboard is visible and on iOS
        Connections {
          target: app
          function onKeyboardVisibleChanged() {
            if(Qt.platform.os === "ios" && app.keyboardVisible) {
              app.callDelayed(250, function() {
                keyboardHelperPanel.visible = true
              })
            } else {
              keyboardHelperPanel.visible = false
            }
          }
        }
    
        Rectangle {
          width: parent.width
          height: px(1)
          color: Theme.dividerColor
        }
    
        AppButton {
          id: inputHelperDoneButton
          text: qsTr("Done")
          anchors.right: parent.right
          height: dp(42)
          flat: true
          fontBold: true
          horizontalMargin: 0
          verticalMargin: 0
          onClicked: page.forceActiveFocus()
        }
      }
    
      // Helper function to call a function after a spcific delay, like JS setTimout
      function callDelayed(interval, callback) {
        let delayCaller = Qt.createQmlObject('import QtQuick; Timer {}', app)
        delayCaller.interval = interval
        delayCaller.triggered.connect( function () {
          if (callback && callback !== undefined) {
            callback()
          }
          delayCaller.destroy()
        })
        delayCaller.start()
      }
    }
    

    Hope this helps.

    Best,
    Alex

    #25006

    Alberto

    Hello Alex,

    Thanks a lot for your time. I ended up with this solution aswell. It works great but it will mess up when there is little space (NativeUtils.SoftInputModeAdjustPan). I also tried to lock SoftInputModeAdjustPan mode, but this will then hide the input.

    Placing the panel to the bottom makes this behaviour.

     

     

    To prevent this issue I just need to know the amount of pixels the SoftInputModeAdjustPan adjusts. Its a dynamic value, so I don’t know where to get it.

    Thanks!

     

    #25007

    Alberto

    This can be fixed adding the keyboard height as bottom margin when SoftInputModeAdjustPan happens. I just need to know when this mode is enabled or not to add or remove the margin. (NativeUtils.softInputMode is always 0)

    #25008

    Alex
    Felgo Team

    Hi Alberto,

    Qt does not provide any info in wheter the pan does occur, nor on the distance panned. Unless your UI does not care about the pan (which is not the case for you), we highly recommend to set https://felgo.com/doc/felgo-nativeutils/#softInputMode-prop to NativeUtils.SoftInputModeAdjustNothing and handle potential overlaps yourself, by e.g. adjusting the contentY of a Flickable programatically.

    Here is an in-depth example of how this could be done:

    import Felgo
    import QtQuick
    
    App {
      id: app
    
      onInitTheme: {
        NativeUtils.softInputMode = NativeUtils.SoftInputModeAdjustNothing
      }
    
      Navigation {
        id: navigation
    
        NavigationItem {
          iconType: IconType.home
          title: "Test"
    
          NavigationStack {
            AppPage {
              id: page
              title: qsTr("My App")
    
              AppFlickable {
                id: flickable
                anchors.fill: parent
                contentWidth: width
                contentHeight: contentWrapper.height
    
                Connections {
                  target: kbdHelper
                  enabled: (Theme.isIos || Theme.isAndroid)
                  function onKbdHeightChanged() {
                    console.log("kbdHelper.kbdHeight",kbdHelper.kbdHeight)
                    if(kbdHelper.kbdHeight > 0) {
                      shiftTimer.start()
                    } else {
                      flickable.bottomMargin = 0
                    }
                  }
                }
    
                Timer {
                  id: shiftTimer
                  interval: 25
                  onTriggered: {
                    // Calculate distance of flickable to bottom of screen
                    const bottomDistance = app.height - flickable.mapToItem(null, 0, 0).y - flickable.height
                    // Add content padding for flickable and shift up flickable if required
                    flickable.bottomMargin = Math.max(0, kbdHelper.kbdHeight - bottomDistance + kbdHelper.kbdShiftOffset)// - (kaToolBar.visible ? kaToolBar.height : 0)// - (footer ? footer.height : 0)//KATheme.calculateShiftOnKbdShow()
                    flickable.contentY = flickable.contentY + kbdHelper.calculateShiftOnKbdShow()
                  }
                }
    
                MouseArea {
                  id: contentWrapper
                  width: parent.width
                  height: dp(1000)
                  onClicked: forceActiveFocus()
    
                  Column {
                    width: parent.width
                    y: dp(500)
                    spacing: dp(10)
                    anchors.horizontalCenter: parent.horizontalCenter
    
                    AppTextField {
                      id: numberField
                      inputMethodHints: Qt.ImhDigitsOnly
                      width: parent.width / 2
                      borderColor: focus ? Theme.tintColor : "black"
                      borderWidth: 1
                      anchors.horizontalCenter: parent.horizontalCenter
                    }
    
                    AppTextField {
                      id: textField
                      width: parent.width / 2
                      borderColor: focus ? Theme.tintColor : "black"
                      borderWidth: 1
                      anchors.horizontalCenter: parent.horizontalCenter
                    }
                  }
                }
              }
            }
          }
        }
      }
    
      // keyboard done helper
      Rectangle {
        id: keyboardHelperPanel
    
        width: parent.width
        height: Qt.inputMethod.keyboardRectangle.height + dp(42)
        anchors.bottom: parent.bottom
        color: Theme.tabBar.backgroundColor
        visible: Qt.platform.os === "ios" && app.keyboardVisible && !Qt.inputMethod.animating
    
        Rectangle {
          width: parent.width
          height: px(1)
          color: Theme.dividerColor
        }
    
        AppButton {
          id: inputHelperDoneButton
          text: qsTr("Done")
          anchors.right: parent.right
          height: dp(42)
          flat: true
          fontBold: true
          horizontalMargin: 0
          verticalMargin: 0
          onClicked: page.forceActiveFocus()
        }
      }
    
      Item {
        id: kbdHelper
    
        readonly property bool kbdVisible: Qt.inputMethod.visible
                                             && (Qt.inputMethod.keyboardRectangle.height > 0 || kbdHelper.kbdHeightAndroid > 0)
                                             && !Qt.inputMethod.animating
        onKbdVisibleChanged: console.log("kbdVisible", kbdVisible)
    
        // Use more accurate keyboard height calculation on Android
        // This is required as Qt reports wrong keyboard height for certain keyboards but correct keyboard y value
        readonly property int kbdHeightAndroid: app ? app.height - Qt.inputMethod.keyboardRectangle.y : 0
        onKbdHeightAndroidChanged: console.log("kbdHeightAndroid", kbdHeightAndroid)
        readonly property int kbdHeight: kbdHelper.kbdVisible ? (Qt.platform.os === "ios" ? Qt.inputMethod.keyboardRectangle.height : kbdHelper.kbdHeightAndroid) : 0
        //readonly property int kbdHeight: Qt.inputMethod.keyboardRectangle.height // not accurate on Android with certain keyboards
        readonly property int kbdY: Qt.inputMethod.keyboardRectangle.y
        readonly property int kbdShiftOffset: dp(100)
    
        function calculateShiftOnKbdShow() {
          if (kbdHelper.kbdVisible) {
            const r = Qt.inputMethod.inputItemClipRectangle
            // In some rare cases, Qt returns negative Y value for the inputItemClipRectangle but abs is correct
            const rY = Math.abs(r.y)
            // If the height is missing, we use a default value, equal to the standard text field
            const rHeight = r.height > 0 ? r.height : (app ? app.dp(35) : 35)
    
            if (rHeight + rY > app.height) {
              // Situation when keyboard shown, but input rect is not correct, in this case no need to shift.
              return 0
            }
    
            // If the keyboard covers the input, we calculate the distance to shift it up
            // We also add an additional default offset, so it does not directly sit on top of the keyboard
            if (rY + rHeight >= kbdHelper.kbdY) {
              return rY + rHeight - kbdHelper.kbdY + kbdHelper.kbdShiftOffset
            }
          }
          return 0
        }
      }
    }
    

     

    Best,
    Alex

    #25009

    Alberto

    Thank you a lot Alex!

    Got it finally working, added some transitions for the contentY change and looks super well.

    Final result 

     

    Have a good day.

    Alberto

    #25010

    Alex
    Felgo Team

    This looks very clean, great work!

    We’ll check if we can offer some of those featuers with the components directly, or add examples to our docs at least, since this is a pretty common use case after all.

    Best,
    Alex

Viewing 7 posts - 1 through 7 (of 7 total)

RSS feed for this thread

You must be logged in to reply to this topic.

Qt_Technology_Partner_RGB_475 Qt_Service_Partner_RGB_475_padded