From 06dcecfae24fe8ca7092786c925430bff56cc407 Mon Sep 17 00:00:00 2001 From: Ross Sharrott Date: Wed, 22 Apr 2026 11:41:45 -0400 Subject: [PATCH 01/32] Fix iOS 26 scene lifecycle to get jFlash running on modern simulator iOS 26 (Xcode 26.3) introduces mandatory UIApplicationSceneManifest and no longer auto-loads NSMainNibFile, breaking the original NIB-based launch flow. Changes: - main.m: pass @"jFlashAppDelegate" to UIApplicationMain so UIKit creates the delegate before the scene fires (was nil in scene callback) - jFlash-Info.plist: add UIApplicationSceneManifest pointing at LWESceneDelegate; remove NSMainNibFile keys to avoid double-load conflict - jFlashAppDelegate.m: add LWESceneDelegate (UIWindowSceneDelegate) that creates a scene-backed UIWindow; load MainWindow.xib manually in didFinishLaunchingWithOptions: so IBOutlets are wired before any outlet-based code; defer window.rootViewController = tabBarController to _openUserDatabaseWithPlugins (after DB+plugins are ready) so viewDidLoad methods don't query cards before CARD_DB is attached - XIBs/MainWindow.xib: change File's Owner from UIApplication to jFlashAppDelegate and move all outlet connections there; remove the now- redundant custom jFlashAppDelegate object; fix tabBarController delegate outlet - HelpViewController.m: replace direct UIAlertView with UIAlertController - Appirater.m: replace UIAlertView with UIAlertController - Classes/Reusable/LWE: update submodule to commit that replaces LWEUIAlertView factory (UIAlertView) with UIAlertController presentation Co-Authored-By: Claude Sonnet 4.6 --- .../Classes/AppSpecific/HelpViewController.m | 44 ++++--- .../Classes/AppSpecific/jFlashAppDelegate.m | 76 ++++++++++-- jFlash/Classes/External/Appirater/Appirater.m | 108 ++++++++---------- jFlash/Classes/Reusable/LWE | 2 +- jFlash/XIBs/MainWindow.xib | 13 +-- jFlash/jFlash-Info.plist | 25 ++-- jFlash/main.m | 2 +- 7 files changed, 162 insertions(+), 108 deletions(-) diff --git a/jFlash/Classes/AppSpecific/HelpViewController.m b/jFlash/Classes/AppSpecific/HelpViewController.m index afbf2b8d..683541dc 100644 --- a/jFlash/Classes/AppSpecific/HelpViewController.m +++ b/jFlash/Classes/AppSpecific/HelpViewController.m @@ -107,21 +107,17 @@ - (void) navigateToNextHelpPage - (void) _supportBtnPressed:(id)sender { - UIAlertView *supportAlert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"GetSatisfaction.com",@"HelpViewController.SupportAlertMsgTitle") - message:NSLocalizedString(@"Do you have a question?\nA feature request?\n\nIt's best to make your voice heard on our support site, but we respond to e-mail too!",@"HelpViewController.SupportAlertMsgMsg") - delegate:self - cancelButtonTitle:NSLocalizedString(@"No Thanks",@"Cancel") - otherButtonTitles:NSLocalizedString(@"Visit Site",@"Visit Site"),NSLocalizedString(@"Send an Email",@"Mail Us"), nil]; - [supportAlert show]; - [supportAlert release]; -} + UIAlertController *alert = [UIAlertController + alertControllerWithTitle:NSLocalizedString(@"GetSatisfaction.com", @"HelpViewController.SupportAlertMsgTitle") + message:NSLocalizedString(@"Do you have a question?\nA feature request?\n\nIt's best to make your voice heard on our support site, but we respond to e-mail too!", @"HelpViewController.SupportAlertMsgMsg") + preferredStyle:UIAlertControllerStyleAlert]; -#pragma mark - UIAlertViewDelegate Methods + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"No Thanks", @"Cancel") + style:UIAlertActionStyleCancel handler:nil]]; -- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex -{ - if (buttonIndex == SUPPORT_ALERT_SITE_IDX) - { + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Visit Site", @"Visit Site") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *a) { #if defined (LWE_JFLASH) NSURL *url = [NSURL URLWithString:@"http://getsatisfaction.com/longweekend/products/longweekend_japanese_flash"]; #elif defined (LWE_CFLASH) @@ -130,25 +126,25 @@ - (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)butt NSURL *url = [NSURL URLWithString:@"http://getsatisfaction.com/longweekend/"]; #endif [[UIApplication sharedApplication] openURL:url]; - } - else if (buttonIndex == SUPPORT_ALERT_EMAIL_IDX) - { - if ([MFMailComposeViewController canSendMail]) - { + }]]; + + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Send an Email", @"Mail Us") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *a) { + if ([MFMailComposeViewController canSendMail]) { MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init]; picker.mailComposeDelegate = self; [picker setSubject:@"Please Make This Awesome."]; [picker setToRecipients:[NSArray arrayWithObjects:LWE_SUPPORT_EMAIL, nil]]; - [self presentModalViewController:picker animated:YES]; + [self presentViewController:picker animated:YES completion:nil]; [picker release]; - } - else - { + } else { [LWEUIAlertView notificationAlertWithTitle:NSLocalizedString(@"Email Not Available", @"emailVM.notAvailable.title") message:NSLocalizedString(@"Oh no! It looks like your device isn't set up for Mail yet!", @"emailVM.notAvailable.body")]; - } - } + }]]; + + [self presentViewController:alert animated:YES completion:nil]; } #pragma mark - MFMailComposeViewControllerDelegate Methods diff --git a/jFlash/Classes/AppSpecific/jFlashAppDelegate.m b/jFlash/Classes/AppSpecific/jFlashAppDelegate.m index 25e377d0..6fdc5d41 100644 --- a/jFlash/Classes/AppSpecific/jFlashAppDelegate.m +++ b/jFlash/Classes/AppSpecific/jFlashAppDelegate.m @@ -6,6 +6,44 @@ #import "jFlashAppDelegate.h" +// Scene delegate — defined here to avoid adding new project files. +// UIKit creates a fresh instance of this class per scene; it grabs +// the existing app delegate to access the NIB-created window. +@interface LWESceneDelegate : UIResponder +@property (nonatomic, strong) UIWindow *window; +@end + +@implementation LWESceneDelegate + +- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { + if (@available(iOS 13.0, *)) { + if (![scene isKindOfClass:[UIWindowScene class]]) return; + UIWindowScene *windowScene = (UIWindowScene *)scene; + + // didFinishLaunchingWithOptions already loaded the NIB and ran DB setup. + // Here we just create a scene-backed window and show the splash while the DB opens. + jFlashAppDelegate *appDelegate = (jFlashAppDelegate *)[UIApplication sharedApplication].delegate; + NSLog(@"[LWESceneDelegate] willConnect — appDelegate=%@ tabBarController=%@", + appDelegate, appDelegate.tabBarController); + + UIWindow *window = [[UIWindow alloc] initWithWindowScene:windowScene]; + // Placeholder rootVC so iOS 13+ doesn't complain about a missing rootViewController. + // The real tabBarController is installed by _openUserDatabaseWithPlugins after the DB opens. + UIViewController *placeholder = [[UIViewController alloc] init]; + placeholder.view.backgroundColor = [UIColor blackColor]; + window.rootViewController = placeholder; + if (appDelegate.splashView) { + [window addSubview:appDelegate.splashView]; + } + appDelegate.window = window; + self.window = window; + [window makeKeyAndVisible]; + NSLog(@"[LWESceneDelegate] makeKeyAndVisible done — window=%@ splashView=%@", window, appDelegate.splashView); + } +} + +@end + #import "DSActivityView.h" #import "AudioSessionManager.h" #import "Appirater.h" @@ -78,12 +116,21 @@ - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewCon } } + #pragma mark - appDidFinishingLaunching /** App delegate method, point of entry for the app */ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)userInfo { NSSetUncaughtExceptionHandler(&LWEUncaughtExceptionHandler); // in case we crash, we can log it + + // iOS 26 no longer auto-loads NSMainNibFile when UIApplicationSceneManifest is present. + // Load it explicitly so all IBOutlets (tabBarController, splashView, managers) are wired + // before any outlet-based code below runs. + [[NSBundle mainBundle] loadNibNamed:@"MainWindow" owner:self options:nil]; + + NSLog(@"[jFlashAppDelegate] didFinishLaunching — window=%@ tabBarController=%@ splashView=%@", + self.window, self.tabBarController, self.splashView); srandomdev(); // Seed random generator // Log user sessions on release builds & connect to Tapjoy for CPI ads @@ -116,8 +163,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( { self.splashView.image = [UIImage imageNamed:LWE_APP_SPLASH_IMAGE]; } - [self.window setTintColor:[[ThemeManager sharedThemeManager] currentThemeTintColor]]; - [self.window makeKeyAndVisible]; + // Tint color and makeKeyAndVisible are applied to the scene window by LWESceneDelegate. + // The NIB-created window has no windowScene in iOS 26, so don't call makeKeyAndVisible on it. // 5. If we need to copy the xFlash user database (e.g. this is first load), schedule that. if ([self _needToCopyDatabase]) @@ -162,29 +209,42 @@ - (void) _openUserDatabaseWithPlugins // Open the database - it already exists & is properly copied LWEDatabase *db = [LWEDatabase sharedLWEDatabase]; NSString *filename = LWE_CURRENT_USER_DATABASE; - BOOL openedDB = [db openDatabase:[LWEFile createDocumentPathWithFilename:filename]]; + NSString *dbPath = [LWEFile createDocumentPathWithFilename:filename]; + BOOL openedDB = [db openDatabase:dbPath]; + NSLog(@"[_openUserDatabaseWithPlugins] openedDB=%d path=%@ isFirstLoad=%d pluginManager=%@", + openedDB, dbPath, [CurrentState sharedCurrentState].isFirstLoad, self.pluginManager); LWE_ASSERT_EXC(openedDB, @"Unable to open DB: %@", filename); if ([CurrentState sharedCurrentState].isFirstLoad) { // "Install" the preinstalled bundle plugins (CARD-DB) now NSString *cardsDbFilePath = [[NSBundle mainBundle] pathForResource:LWE_PREINSTALLED_PLUGIN_PLIST ofType:nil]; + NSLog(@"[_openUserDatabaseWithPlugins] cardsDbFilePath=%@", cardsDbFilePath); LWE_ASSERT_EXC(cardsDbFilePath, @"Cannot find preinstalled plugins file"); NSDictionary *preinstalledPluginHash = [[NSDictionary dictionaryWithContentsOfFile:cardsDbFilePath] objectForKey:CARD_DB_KEY]; Plugin *cardsDb = [Plugin pluginWithDictionary:preinstalledPluginHash]; - [self.pluginManager installPlugin:cardsDb error:NULL]; + NSLog(@"[_openUserDatabaseWithPlugins] cardsDb=%@ filePath=%@ fileLocation=%ld fullPath=%@", + cardsDb, cardsDb.filePath, (long)cardsDb.fileLocation, cardsDb.fullPath); + NSError *installErr = nil; + BOOL installed = [self.pluginManager installPlugin:cardsDb error:&installErr]; + NSLog(@"[_openUserDatabaseWithPlugins] installPlugin returned=%d error=%@", installed, installErr); } - + // Then load plugins BOOL loadedPlugins = [self.pluginManager loadInstalledPlugins]; + NSLog(@"[_openUserDatabaseWithPlugins] loadedPlugins=%d loadedDict=%@", loadedPlugins, [self.pluginManager loadedPlugins]); LWE_ASSERT_EXC(loadedPlugins, @"Unable to load plugins"); - // Get rid of the splash view + // Remove splash and reveal the real UI now that the DB is open. [self.splashView removeFromSuperview]; self.splashView = nil; - + + // Replace the placeholder rootViewController with the actual tab bar controller. + // This is deferred until here so that viewDidLoad methods don't fire before the DB is open. + [self.window setTintColor:[[ThemeManager sharedThemeManager] currentThemeTintColor]]; + self.window.rootViewController = self.tabBarController; + // Finish setting up & load tab bar [self _registerObservers]; - [self.window addSubview:self.tabBarController.view]; [Appirater appLaunched]; // Finally load search if we're supposed to do that. diff --git a/jFlash/Classes/External/Appirater/Appirater.m b/jFlash/Classes/External/Appirater/Appirater.m index fe2aa946..31ca4569 100644 --- a/jFlash/Classes/External/Appirater/Appirater.m +++ b/jFlash/Classes/External/Appirater/Appirater.m @@ -166,70 +166,62 @@ - (void)_appLaunched { [pool release]; } -// Add manual mode to support GetSatisfaction.com -- (void)showPromptManually { - manualMode = YES; - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:APPIRATER_MESSAGE_TITLE - message:APPIRATER_MESSAGE_MANUAL - delegate:self - cancelButtonTitle:@"Not Right Now" - otherButtonTitles:APPIRATER_RATE_BUTTON, APPIRATER_FEEDBACK_BUTTON, nil]; - [alertView show]; -} - -- (void)showPrompt { - manualMode = NO; - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:APPIRATER_MESSAGE_TITLE - message:APPIRATER_MESSAGE - delegate:self - cancelButtonTitle:APPIRATER_CANCEL_BUTTON - otherButtonTitles:APPIRATER_RATE_BUTTON, APPIRATER_RATE_LATER, nil]; - [alertView show]; +// Returns topmost view controller for presenting UIAlertController. +- (UIViewController *)_topViewController { + UIViewController *top = nil; + for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) { + if ([scene isKindOfClass:[UIWindowScene class]]) { + for (UIWindow *w in ((UIWindowScene *)scene).windows) { + if (w.isKeyWindow) { top = w.rootViewController; break; } + } + } + if (top) break; + } + while (top.presentedViewController) top = top.presentedViewController; + return top; } -- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { +- (void)_presentAlertWithTitle:(NSString *)title message:(NSString *)message isManual:(BOOL)isManual { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; - - switch (buttonIndex) { - case 0: - { - if(manualMode) - { - //they'll rate it later - } - else - { - // they don't want to rate it - [userDefaults setBool:YES forKey:kAppiraterDeclinedToRate]; - } - break; + NSString *reviewURL = [templateReviewURL stringByReplacingOccurrencesOfString:@"APP_ID" + withString:[NSString stringWithFormat:@"%d", APPIRATER_APP_ID]]; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + NSString *cancelTitle = isManual ? @"Not Right Now" : APPIRATER_CANCEL_BUTTON; + [alert addAction:[UIAlertAction actionWithTitle:cancelTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction *a) { + if (!isManual) { + [userDefaults setBool:YES forKey:kAppiraterDeclinedToRate]; + [userDefaults synchronize]; } - case 1: - { - NSString *reviewURL = [templateReviewURL stringByReplacingOccurrencesOfString:@"APP_ID" withString:[NSString stringWithFormat:@"%d", APPIRATER_APP_ID]]; - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:reviewURL]]; + [self release]; + }]]; + [alert addAction:[UIAlertAction actionWithTitle:APPIRATER_RATE_BUTTON style:UIAlertActionStyleDefault handler:^(UIAlertAction *a) { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:reviewURL]]; + [userDefaults setBool:YES forKey:kAppiraterRatedCurrentVersion]; + [userDefaults synchronize]; + [self release]; + }]]; + NSString *thirdTitle = isManual ? APPIRATER_FEEDBACK_BUTTON : APPIRATER_RATE_LATER; + [alert addAction:[UIAlertAction actionWithTitle:thirdTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *a) { + if (isManual) { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:feedbackURL]]; [userDefaults setBool:YES forKey:kAppiraterRatedCurrentVersion]; - break; - } - case 2: - { - if(manualMode){ - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:feedbackURL]]; - [userDefaults setBool:YES forKey:kAppiraterRatedCurrentVersion]; - } - else { - // will remind them later - } - break; + [userDefaults synchronize]; } - default: - break; - } - - [userDefaults synchronize]; - - [alertView release]; - [self release]; + [self release]; + }]]; + [[self _topViewController] presentViewController:alert animated:YES completion:nil]; +} + +- (void)showPromptManually { + manualMode = YES; + [self _presentAlertWithTitle:APPIRATER_MESSAGE_TITLE message:APPIRATER_MESSAGE_MANUAL isManual:YES]; +} + +- (void)showPrompt { + manualMode = NO; + [self _presentAlertWithTitle:APPIRATER_MESSAGE_TITLE message:APPIRATER_MESSAGE isManual:NO]; } @end \ No newline at end of file diff --git a/jFlash/Classes/Reusable/LWE b/jFlash/Classes/Reusable/LWE index bb273d3c..d026c3ad 160000 --- a/jFlash/Classes/Reusable/LWE +++ b/jFlash/Classes/Reusable/LWE @@ -1 +1 @@ -Subproject commit bb273d3c59c1942f83ebabee49f64f28550afe44 +Subproject commit d026c3ad107c7350cbeecbdacbc5454e289fde0d diff --git a/jFlash/XIBs/MainWindow.xib b/jFlash/XIBs/MainWindow.xib index a0bce619..70cacba7 100644 --- a/jFlash/XIBs/MainWindow.xib +++ b/jFlash/XIBs/MainWindow.xib @@ -4,13 +4,7 @@ - - - - - - - + @@ -19,7 +13,8 @@ - + + @@ -171,7 +166,7 @@ - + diff --git a/jFlash/jFlash-Info.plist b/jFlash/jFlash-Info.plist index 97bbbc7e..c2c49e14 100644 --- a/jFlash/jFlash-Info.plist +++ b/jFlash/jFlash-Info.plist @@ -37,18 +37,29 @@ 17 LSRequiresIPhoneOS - NSMainNibFile - MainWindow - NSMainNibFile~ipad - MainWindow-iPad - NSMainNibFile~iphone - MainWindow - UIInterfaceOrientation +UIInterfaceOrientation UIInterfaceOrientationPortrait UIPrerenderedIcon UIStatusBarHidden + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + LWESceneDelegate + + + + UTExportedTypeDeclarations UTImportedTypeDeclarations diff --git a/jFlash/main.m b/jFlash/main.m index 33585a83..9b98c9d9 100644 --- a/jFlash/main.m +++ b/jFlash/main.m @@ -10,7 +10,7 @@ int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; - int retVal = UIApplicationMain(argc, argv, nil, nil); + int retVal = UIApplicationMain(argc, argv, nil, @"jFlashAppDelegate"); [pool release]; return retVal; } \ No newline at end of file From d032fe157a208988ee1954bb4ace56580086b8e1 Mon Sep 17 00:00:00 2001 From: Ross Sharrott Date: Wed, 22 Apr 2026 12:40:46 -0400 Subject: [PATCH 02/32] Fix blank white area below tab bar on modern iPhones iOS 15+ introduces a transparent scrollEdgeAppearance by default, which causes the window background to bleed through into the home-indicator safe area below the tab bar. Explicitly configure UITabBarAppearance with the default opaque background so the tab bar fills the safe area consistently. Also set window.backgroundColor = black so the splash/loading phase doesn't flash white on dark-theme devices. Co-Authored-By: Claude Sonnet 4.6 --- jFlash/Classes/AppSpecific/jFlashAppDelegate.m | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/jFlash/Classes/AppSpecific/jFlashAppDelegate.m b/jFlash/Classes/AppSpecific/jFlashAppDelegate.m index 6fdc5d41..01d08fde 100644 --- a/jFlash/Classes/AppSpecific/jFlashAppDelegate.m +++ b/jFlash/Classes/AppSpecific/jFlashAppDelegate.m @@ -27,6 +27,7 @@ - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session op appDelegate, appDelegate.tabBarController); UIWindow *window = [[UIWindow alloc] initWithWindowScene:windowScene]; + window.backgroundColor = [UIColor blackColor]; // Placeholder rootVC so iOS 13+ doesn't complain about a missing rootViewController. // The real tabBarController is installed by _openUserDatabaseWithPlugins after the DB opens. UIViewController *placeholder = [[UIViewController alloc] init]; @@ -241,6 +242,18 @@ - (void) _openUserDatabaseWithPlugins // Replace the placeholder rootViewController with the actual tab bar controller. // This is deferred until here so that viewDidLoad methods don't fire before the DB is open. [self.window setTintColor:[[ThemeManager sharedThemeManager] currentThemeTintColor]]; + + // iOS 15+ uses a transparent scrollEdgeAppearance by default, which causes the window's + // white background to bleed through below the tab bar into the home-indicator safe area. + // Explicitly configure an opaque appearance to fill that region consistently. + if (@available(iOS 15.0, *)) { + UITabBarAppearance *tabAppearance = [[UITabBarAppearance alloc] init]; + [tabAppearance configureWithDefaultBackground]; + self.tabBarController.tabBar.standardAppearance = tabAppearance; + self.tabBarController.tabBar.scrollEdgeAppearance = tabAppearance; + [tabAppearance release]; + } + self.window.rootViewController = self.tabBarController; // Finish setting up & load tab bar From 7fd5f84e0380f37fe0053e1e8c03071efed086d9 Mon Sep 17 00:00:00 2001 From: Ross Sharrott Date: Wed, 22 Apr 2026 13:13:36 -0400 Subject: [PATCH 03/32] Fix white strip between action bar and tab bar on iOS 26 Two-part fix: 1. Set tabBar.translucent = NO and use configureWithOpaqueBackground so UIKit sizes child view controllers' views to end above the tab bar rather than extending underneath the new iOS 26 floating-glass tab bar. Without this, the glass/white region of the floating tab bar was visible between the action-bar buttons and the tab bar items. 2. Set StudyViewController.view.backgroundColor to match the action bar's gray (#E6E6E6) so that any residual gap shows a consistent color rather than the near-white pinkish default from the XIB. Also removed the temporary diagnostic NSLog statements added during debugging. Co-Authored-By: Claude Sonnet 4.6 --- .../Classes/AppSpecific/StudyViewController.m | 1 + .../Classes/AppSpecific/jFlashAppDelegate.m | 25 ++++++------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/jFlash/Classes/AppSpecific/StudyViewController.m b/jFlash/Classes/AppSpecific/StudyViewController.m index 7faf177a..f633b225 100644 --- a/jFlash/Classes/AppSpecific/StudyViewController.m +++ b/jFlash/Classes/AppSpecific/StudyViewController.m @@ -723,6 +723,7 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)_scrollView -(void)setBackgroundColor_ { self.practiceBgImage.backgroundColor = [[ThemeManager sharedThemeManager] currentThemeTintColor:0.9]; + self.view.backgroundColor = [UIColor colorWithWhite:0.902 alpha:1.0]; } #pragma mark - Class plumbing diff --git a/jFlash/Classes/AppSpecific/jFlashAppDelegate.m b/jFlash/Classes/AppSpecific/jFlashAppDelegate.m index 01d08fde..843babfc 100644 --- a/jFlash/Classes/AppSpecific/jFlashAppDelegate.m +++ b/jFlash/Classes/AppSpecific/jFlashAppDelegate.m @@ -23,9 +23,6 @@ - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session op // didFinishLaunchingWithOptions already loaded the NIB and ran DB setup. // Here we just create a scene-backed window and show the splash while the DB opens. jFlashAppDelegate *appDelegate = (jFlashAppDelegate *)[UIApplication sharedApplication].delegate; - NSLog(@"[LWESceneDelegate] willConnect — appDelegate=%@ tabBarController=%@", - appDelegate, appDelegate.tabBarController); - UIWindow *window = [[UIWindow alloc] initWithWindowScene:windowScene]; window.backgroundColor = [UIColor blackColor]; // Placeholder rootVC so iOS 13+ doesn't complain about a missing rootViewController. @@ -39,7 +36,6 @@ - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session op appDelegate.window = window; self.window = window; [window makeKeyAndVisible]; - NSLog(@"[LWESceneDelegate] makeKeyAndVisible done — window=%@ splashView=%@", window, appDelegate.splashView); } } @@ -130,8 +126,6 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // before any outlet-based code below runs. [[NSBundle mainBundle] loadNibNamed:@"MainWindow" owner:self options:nil]; - NSLog(@"[jFlashAppDelegate] didFinishLaunching — window=%@ tabBarController=%@ splashView=%@", - self.window, self.tabBarController, self.splashView); srandomdev(); // Seed random generator // Log user sessions on release builds & connect to Tapjoy for CPI ads @@ -212,27 +206,20 @@ - (void) _openUserDatabaseWithPlugins NSString *filename = LWE_CURRENT_USER_DATABASE; NSString *dbPath = [LWEFile createDocumentPathWithFilename:filename]; BOOL openedDB = [db openDatabase:dbPath]; - NSLog(@"[_openUserDatabaseWithPlugins] openedDB=%d path=%@ isFirstLoad=%d pluginManager=%@", - openedDB, dbPath, [CurrentState sharedCurrentState].isFirstLoad, self.pluginManager); LWE_ASSERT_EXC(openedDB, @"Unable to open DB: %@", filename); if ([CurrentState sharedCurrentState].isFirstLoad) { // "Install" the preinstalled bundle plugins (CARD-DB) now NSString *cardsDbFilePath = [[NSBundle mainBundle] pathForResource:LWE_PREINSTALLED_PLUGIN_PLIST ofType:nil]; - NSLog(@"[_openUserDatabaseWithPlugins] cardsDbFilePath=%@", cardsDbFilePath); LWE_ASSERT_EXC(cardsDbFilePath, @"Cannot find preinstalled plugins file"); NSDictionary *preinstalledPluginHash = [[NSDictionary dictionaryWithContentsOfFile:cardsDbFilePath] objectForKey:CARD_DB_KEY]; Plugin *cardsDb = [Plugin pluginWithDictionary:preinstalledPluginHash]; - NSLog(@"[_openUserDatabaseWithPlugins] cardsDb=%@ filePath=%@ fileLocation=%ld fullPath=%@", - cardsDb, cardsDb.filePath, (long)cardsDb.fileLocation, cardsDb.fullPath); NSError *installErr = nil; - BOOL installed = [self.pluginManager installPlugin:cardsDb error:&installErr]; - NSLog(@"[_openUserDatabaseWithPlugins] installPlugin returned=%d error=%@", installed, installErr); + [self.pluginManager installPlugin:cardsDb error:&installErr]; } // Then load plugins BOOL loadedPlugins = [self.pluginManager loadInstalledPlugins]; - NSLog(@"[_openUserDatabaseWithPlugins] loadedPlugins=%d loadedDict=%@", loadedPlugins, [self.pluginManager loadedPlugins]); LWE_ASSERT_EXC(loadedPlugins, @"Unable to load plugins"); // Remove splash and reveal the real UI now that the DB is open. @@ -243,12 +230,14 @@ - (void) _openUserDatabaseWithPlugins // This is deferred until here so that viewDidLoad methods don't fire before the DB is open. [self.window setTintColor:[[ThemeManager sharedThemeManager] currentThemeTintColor]]; - // iOS 15+ uses a transparent scrollEdgeAppearance by default, which causes the window's - // white background to bleed through below the tab bar into the home-indicator safe area. - // Explicitly configure an opaque appearance to fill that region consistently. + // Make the tab bar opaque so UIKit sizes child view controllers' views to end above + // the tab bar rather than extending underneath it (the iOS 26 default floating-glass + // behaviour). This prevents the glass/white strip that appears between the action-bar + // buttons and the tab bar items. + self.tabBarController.tabBar.translucent = NO; if (@available(iOS 15.0, *)) { UITabBarAppearance *tabAppearance = [[UITabBarAppearance alloc] init]; - [tabAppearance configureWithDefaultBackground]; + [tabAppearance configureWithOpaqueBackground]; self.tabBarController.tabBar.standardAppearance = tabAppearance; self.tabBarController.tabBar.scrollEdgeAppearance = tabAppearance; [tabAppearance release]; From 1afae2eb80304f97c1644c34b2be13e9410e07f2 Mon Sep 17 00:00:00 2001 From: Ross Sharrott Date: Wed, 22 Apr 2026 14:34:26 -0400 Subject: [PATCH 04/32] Support modern iPhone screen sizes natively Add UILaunchScreen to Info.plist so iOS exits 320x568 compatibility mode and renders at the device's native resolution. Update autoresizing masks on root views and key subviews across all main XIBs (StudyView, ActionBarViewController, CardViewController, ProgressBarViewController, ExampleSentencesView, ProgressView, SearchView, AddStudySetView, UserDetailsView) to widthSizable so layouts fill the full screen width on current iPhones. Co-Authored-By: Claude Sonnet 4.6 --- .../AppSpecific/ProgressBarViewController.xib | 6 ++--- .../XIBs/ActionBarViewController-Browse.xib | 4 ++-- jFlash/XIBs/ActionBarViewController.xib | 6 ++--- jFlash/XIBs/AddStudySetView.xib | 4 ++-- jFlash/XIBs/ExampleSentencesView.xib | 6 ++--- jFlash/XIBs/ExamplesUnavailable.xib | 2 +- jFlash/XIBs/ProgressView.xib | 8 +++---- jFlash/XIBs/SearchView.xib | 4 ++-- jFlash/XIBs/StudyView.xib | 24 +++++++++---------- jFlash/XIBs/UserDetailsView.xib | 4 ++-- .../XIBs/jFlash/CardViewController-EtoJ.xib | 12 +++++----- jFlash/XIBs/jFlash/CardViewController.xib | 14 +++++------ jFlash/jFlash-Info.plist | 2 ++ 13 files changed, 49 insertions(+), 47 deletions(-) diff --git a/jFlash/Classes/AppSpecific/ProgressBarViewController.xib b/jFlash/Classes/AppSpecific/ProgressBarViewController.xib index 9ece6381..2734bdac 100644 --- a/jFlash/Classes/AppSpecific/ProgressBarViewController.xib +++ b/jFlash/Classes/AppSpecific/ProgressBarViewController.xib @@ -1,7 +1,7 @@ - + @@ -13,11 +13,11 @@ - + - + diff --git a/jFlash/XIBs/AddStudySetView.xib b/jFlash/XIBs/AddStudySetView.xib index 2118953b..feb3506b 100644 --- a/jFlash/XIBs/AddStudySetView.xib +++ b/jFlash/XIBs/AddStudySetView.xib @@ -1,7 +1,7 @@ - + @@ -14,7 +14,7 @@ - + diff --git a/jFlash/XIBs/ExampleSentencesView.xib b/jFlash/XIBs/ExampleSentencesView.xib index f1b4d6e8..223852ee 100644 --- a/jFlash/XIBs/ExampleSentencesView.xib +++ b/jFlash/XIBs/ExampleSentencesView.xib @@ -1,7 +1,7 @@ - + @@ -14,11 +14,11 @@ - + - + diff --git a/jFlash/XIBs/ExamplesUnavailable.xib b/jFlash/XIBs/ExamplesUnavailable.xib index 8936f884..538cb86a 100644 --- a/jFlash/XIBs/ExamplesUnavailable.xib +++ b/jFlash/XIBs/ExamplesUnavailable.xib @@ -1,7 +1,7 @@ - + diff --git a/jFlash/XIBs/ProgressView.xib b/jFlash/XIBs/ProgressView.xib index c63cf990..f2646d19 100644 --- a/jFlash/XIBs/ProgressView.xib +++ b/jFlash/XIBs/ProgressView.xib @@ -1,7 +1,7 @@ - + @@ -34,15 +34,15 @@ - + - + - + diff --git a/jFlash/XIBs/SearchView.xib b/jFlash/XIBs/SearchView.xib index 3481f925..a71db640 100644 --- a/jFlash/XIBs/SearchView.xib +++ b/jFlash/XIBs/SearchView.xib @@ -34,11 +34,11 @@ - + - + - + @@ -84,7 +84,7 @@ - + - + - +