From 2eeb41f187af6b9fdb0834aac7347b8684ea0af8 Mon Sep 17 00:00:00 2001 From: bu-tokumitsu Date: Wed, 12 Aug 2026 13:09:56 +0900 Subject: [PATCH 1/3] Adopt UIScene-based lifecycle in example-swift for iOS 27 Add SceneDelegate to the UIKit example so it follows the scene-based lifecycle Apple recommends for iOS 27. Move URL open handling for the 3-D Secure redirect from AppDelegate to SceneDelegate.openURLContexts, declare the scene manifest in both Info.plist variants (SPM and Carthage), and align example-swift-carthage's deployment target with example-swift at iOS 15.0. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../example-swift-carthage-Info.plist | 21 ++++++++- .../example-swift.xcodeproj/project.pbxproj | 18 +++++--- example-swift/example-swift/AppDelegate.swift | 26 +++-------- example-swift/example-swift/Info.plist | 21 ++++++++- .../example-swift/SceneDelegate.swift | 46 +++++++++++++++++++ 5 files changed, 103 insertions(+), 29 deletions(-) create mode 100644 example-swift/example-swift/SceneDelegate.swift diff --git a/example-swift/example-swift-carthage-Info.plist b/example-swift/example-swift-carthage-Info.plist index 447a7dca..cdfb5eb0 100644 --- a/example-swift/example-swift-carthage-Info.plist +++ b/example-swift/example-swift-carthage-Info.plist @@ -31,10 +31,27 @@ 1 LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7 diff --git a/example-swift/example-swift.xcodeproj/project.pbxproj b/example-swift/example-swift.xcodeproj/project.pbxproj index b9bdded2..b91dd1e9 100644 --- a/example-swift/example-swift.xcodeproj/project.pbxproj +++ b/example-swift/example-swift.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 3219F14C26B1087400D2AE00 /* CardFormViewWith3DSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3219F14B26B1087400D2AE00 /* CardFormViewWith3DSViewController.swift */; }; 32B471E024C6AD180061579C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B471DF24C6AD180061579C /* AppDelegate.swift */; }; 32B471E224C6AD180061579C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B471E124C6AD180061579C /* SceneDelegate.swift */; }; + 07A2FE022F8B0F0000000002 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07A2FE012F8B0F0000000001 /* SceneDelegate.swift */; }; + 07A2FE032F8B0F0000000003 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07A2FE012F8B0F0000000001 /* SceneDelegate.swift */; }; 32B471E424C6AD180061579C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B471E324C6AD180061579C /* ContentView.swift */; }; 32B471E624C6AD1B0061579C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 32B471E524C6AD1B0061579C /* Assets.xcassets */; }; 32B471E924C6AD1B0061579C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 32B471E824C6AD1B0061579C /* Preview Assets.xcassets */; }; @@ -101,6 +103,7 @@ 32B471ED24C6AD1B0061579C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32CB11241FDA7E3D007AD8F5 /* example-swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example-swift.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 32CB11271FDA7E3D007AD8F5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 07A2FE012F8B0F0000000001 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 32CB11291FDA7E3D007AD8F5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 32CB112C1FDA7E3D007AD8F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 32CB112E1FDA7E3D007AD8F5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -195,6 +198,7 @@ isa = PBXGroup; children = ( 32CB11271FDA7E3D007AD8F5 /* AppDelegate.swift */, + 07A2FE012F8B0F0000000001 /* SceneDelegate.swift */, 32CB112E1FDA7E3D007AD8F5 /* Assets.xcassets */, ED50839223278BB800C64A7F /* CardFormViewExampleViewController.swift */, ED5085BC232A0FE300C64A7F /* CardFormViewScrollViewController.swift */, @@ -442,6 +446,7 @@ 32CB112A1FDA7E3D007AD8F5 /* ViewController.swift in Sources */, ED50839323278BB800C64A7F /* CardFormViewExampleViewController.swift in Sources */, 32CB11281FDA7E3D007AD8F5 /* AppDelegate.swift in Sources */, + 07A2FE022F8B0F0000000002 /* SceneDelegate.swift in Sources */, EDADA266238CBE780057FDF3 /* SampleService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -458,6 +463,7 @@ 710E2C342F71AA480063E80D /* ViewController.swift in Sources */, 710E2C352F71AA480063E80D /* CardFormViewExampleViewController.swift in Sources */, 710E2C362F71AA480063E80D /* AppDelegate.swift in Sources */, + 07A2FE032F8B0F0000000003 /* SceneDelegate.swift in Sources */, 710E2C372F71AA480063E80D /* SampleService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -677,7 +683,7 @@ DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "example-swift/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -698,7 +704,7 @@ DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "example-swift/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -719,7 +725,7 @@ DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "example-swift-carthage-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -740,7 +746,7 @@ DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "example-swift-carthage-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -796,11 +802,11 @@ /* Begin XCLocalSwiftPackageReference section */ 710E2C2A2F71A9F20063E80D /* XCLocalSwiftPackageReference ".." */ = { isa = XCLocalSwiftPackageReference; - relativePath = ".."; + relativePath = ..; }; 71BF9BC62F7D292400A6E267 /* XCLocalSwiftPackageReference ".." */ = { isa = XCLocalSwiftPackageReference; - relativePath = ".."; + relativePath = ..; }; /* End XCLocalSwiftPackageReference section */ diff --git a/example-swift/example-swift/AppDelegate.swift b/example-swift/example-swift/AppDelegate.swift index 661414dd..0527bfb4 100644 --- a/example-swift/example-swift/AppDelegate.swift +++ b/example-swift/example-swift/AppDelegate.swift @@ -16,8 +16,6 @@ let App3DSRedirectURLKey = "swift-app" @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil @@ -32,26 +30,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } - func application(_ app: UIApplication, - open url: URL, - options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { - - return ThreeDSecureProcessHandler.shared.completeThreeDSecureProcess(url: url) - } - - func applicationWillResignActive(_ application: UIApplication) { - } - - func applicationDidEnterBackground(_ application: UIApplication) { - } - - func applicationWillEnterForeground(_ application: UIApplication) { - } + // MARK: UISceneSession Lifecycle - func applicationDidBecomeActive(_ application: UIApplication) { + func application(_ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions) -> UISceneConfiguration { + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } - func applicationWillTerminate(_ application: UIApplication) { + func application(_ application: UIApplication, + didDiscardSceneSessions sceneSessions: Set) { } } diff --git a/example-swift/example-swift/Info.plist b/example-swift/example-swift/Info.plist index 9e570376..70b047ff 100644 --- a/example-swift/example-swift/Info.plist +++ b/example-swift/example-swift/Info.plist @@ -20,10 +20,27 @@ 1 LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main CFBundleURLTypes diff --git a/example-swift/example-swift/SceneDelegate.swift b/example-swift/example-swift/SceneDelegate.swift new file mode 100644 index 00000000..1ccafda7 --- /dev/null +++ b/example-swift/example-swift/SceneDelegate.swift @@ -0,0 +1,46 @@ +// +// SceneDelegate.swift +// example-swift +// +// Copyright © 2026 PAY, Inc. All rights reserved. +// + +import UIKit +import PAYJP + +@available(iOS 13.0, *) +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene(_ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions) { + guard scene is UIWindowScene else { return } + + if let urlContext = connectionOptions.urlContexts.first { + ThreeDSecureProcessHandler.shared.completeThreeDSecureProcess(url: urlContext.url) + } + } + + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + guard let urlContext = URLContexts.first else { return } + ThreeDSecureProcessHandler.shared.completeThreeDSecureProcess(url: urlContext.url) + } + + func sceneDidDisconnect(_ scene: UIScene) { + } + + func sceneDidBecomeActive(_ scene: UIScene) { + } + + func sceneWillResignActive(_ scene: UIScene) { + } + + func sceneWillEnterForeground(_ scene: UIScene) { + } + + func sceneDidEnterBackground(_ scene: UIScene) { + } + +} From ca16d59c3879b80eaad46c1ceb8126aabef89dc6 Mon Sep 17 00:00:00 2001 From: bu-tokumitsu Date: Wed, 12 Aug 2026 14:58:55 +0900 Subject: [PATCH 2/3] Compile Assets.xcassets via SwiftPM resource processing Switch the SPM resource declaration for Assets.xcassets from .copy to .process so actool runs and Assets.car is emitted into the bundle. Under Xcode 27 the previous .copy declaration no longer produced a compiled asset catalog, causing CardBrand.logoImage and other UIImage(named:in: .module) lookups to return nil and rendering the CardFormViewController brand logos blank (payjp/payjp-ios#116). Co-Authored-By: Claude Opus 4.7 (1M context) --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index aa4f1ada..ad195a27 100644 --- a/Package.swift +++ b/Package.swift @@ -33,7 +33,7 @@ let package = Package( resources: [ .process("Resources/Views"), .process("Resources/Resource.bundle"), - .copy("Resources/Assets.xcassets") + .process("Resources/Assets.xcassets") ] ) ] From 0270e6c6a320e85930b2319ca3c9701678612c5e Mon Sep 17 00:00:00 2001 From: bu-tokumitsu Date: Thu, 13 Aug 2026 15:38:27 +0900 Subject: [PATCH 3/3] Fix duplicate submit button display on iOS 27 simulator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch the bottom submitButton visibility toggle from keyboard show/hide notifications to UITextField begin/end editing notifications. The accessorySubmitButton is attached as the text fields' inputAccessoryView, so its visibility is bound to first responder state — not keyboard frame changes. On the iOS 27 simulator with a hardware keyboard connected, the software keyboard does not appear (so keyboardWillShow does not fire), but the inputAccessoryView still shows, causing both buttons to be visible at once. Co-Authored-By: Claude Opus 4.7 (1M context) --- Sources/Views/CardFormViewController.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Views/CardFormViewController.swift b/Sources/Views/CardFormViewController.swift index f3294dca..e4e53ba1 100644 --- a/Sources/Views/CardFormViewController.swift +++ b/Sources/Views/CardFormViewController.swift @@ -174,11 +174,11 @@ public class CardFormViewController: UIViewController { createToken() } - @objc private func handleKeyboardShow(notification: Notification) { + @objc private func textFieldDidBeginEditing(notification: Notification) { submitButton.isHidden = true } - @objc private func handleKeyboardHide(notification: Notification) { + @objc private func textFieldDidEndEditing(notification: Notification) { submitButton.isHidden = false } @@ -273,12 +273,12 @@ public class CardFormViewController: UIViewController { private func setupKeyboardNotification() { NotificationCenter.default.addObserver(self, - selector: #selector(handleKeyboardShow), - name: UIResponder.keyboardWillShowNotification, + selector: #selector(textFieldDidBeginEditing), + name: UITextField.textDidBeginEditingNotification, object: nil) NotificationCenter.default.addObserver(self, - selector: #selector(handleKeyboardHide), - name: UIResponder.keyboardWillHideNotification, + selector: #selector(textFieldDidEndEditing), + name: UITextField.textDidEndEditingNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame),