I was trying to keep the answer short. That’s why I said it’s “supposed to attempt pairing” :-)
Here’s the long version: the only way a peripheral can signal to initiate a pairing flow is by returning Insufficient Auth when an app tries to retrieve, read, or write a characteristic. If there’s an error or if the user taps on ‘Cancel’ on the pair dialog an error value is returned in the delegate.
For a read operation it would be in the didUpdateValueForCharacteristic delegate method. For a write in didWriteValueForDescriptor delegate. If the GATT descriptor is set up as protected then the error may also come back in didDiscoverDescriptorsForCharacteristic or in didUpdateNotificationStateForCharacteris after trying to set a characteristic for notification. An app should always check those error values and handle them properly.
My own experience has been that if iOS thinks it’s paired but the device isn’t it *may* put up the pairing dialog. That seems to vary depending on how far each side got in the key exchange process, whether they’re pairing or bonding, whether the pairing is set up for Just Works or MITM prevention,or sometimes even on the BLE module (TI vs. Nordic). But there’s no easy way to tell on the phone side what happened. I’ve found it best to try a few times on the app side then if it’s still returning an error signal the user and try to start with a clean slate.
The safest bet is if any of these methods return a non-nil error value then the app can choose to either try it again (which *might* force the pair dialog to pop up again) or the app could alert the user that the pairing has failed and they should take action.
Unfortunately, the user instructions have to be pretty generic if things didn’t work out. Along the lines of: “Connection failed, please restart your device and go to Bluetooth Settings and Forget device then try pairing again” — blech.
If someone has a better suggestion on how to handle pairing failures I’d be interested in hearing about it.