2020 solution
Six steps,
1. library

2. add to AppDelegate
// APNs:
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print(">> getting an APNs token works >>\(deviceToken)<<")
let tokenAsText = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
}
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
print(">> error getting APNs token >>\(error)<<")
}
3. in your app
For example, after user logon
import UserNotifications
and
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
askNotifications()
}
func askNotifications() {
let n = UNUserNotificationCenter.current()
n.requestAuthorization(options: [.alert, .sound, .badge]) {
granted, error in
print("result \(granted) \(String(describing: error))")
guard granted else {
print("not granted!")
return
}
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
4. capabilities tab
do three items

beware of old articles: there is no longer an "on switch" there.
5. tethered phone, no simulator
it will not work in simulator. must be a phone

tethered phone is fine, console will work fine
6. and finally ... WIFI DANCE
With 100% repeatability, as of 2020 you MUST do the following:
- completely erase app from tethered phone
- build to the phone and run from Xcode
(it definitely won't work)
- force quit the app
- completely erase app from tethered phone
- turn off both wifi/cell
- build to the phone and run from Xcode
(obviously it won't work)
- force quit the app
then in exactly this order:
- completely erase app from tethered phone
- turn on connectivity (either wifi or cell is fine - no problem)
- build to the phone and run from Xcode
It now works. (And will work from here onwards.)
The WIFI DANCE is just one of those weird Apple things. They have not fixed it yet (Xcode 11.3). In fact it has become "100% repeatable": you have to do the WIFI DANCE.