Home Blog

ios – Learn how to apply non-uniform scaling to a rotated UIView (rectangle) over Mapbox with out distortion?


I’m making an attempt to permit non-uniform scaling (horizontal or vertical) of a UIView rectangle that’s positioned over a Mapbox mapView and has already been rotated. Uniform scaling works properly, however once I try to use non-uniform scaling (scaling solely X or Y) utilizing transforms after rotation, the rectangle turns into distorted like a parallelogram.

To keep away from distortion, I as an alternative redraw the rectangle utilizing coordinate math on the finish of the gesture, however this makes the scaling really feel abrupt and never clean. I need to obtain clean, stay non-uniform scaling after rotation with out visible distortion. I’ve tried utilizing rework.scaledBy(x:y:), however it doesn’t preserve the rectangle’s orthogonality post-rotation.

How can I obtain clean, interactive, non-uniform scaling on a rotated view with out it deforming?

Code Snippets

Right here’s the pinchProcess gesture handler:

@objc func handlePinchGesture(_ recognizer: UIPinchGestureRecognizer) {
    guard isScalingEnabled, let targetView = self.scalingView else { return }

    if recognizer.state == .started {
        previousScale = 1.0
        initialPinchLocation = recognizer.location(in: targetView)
    }

    if recognizer.state == .ended || recognizer.state == .cancelled {
        if hasRotationApplied {
            gestureDelegate?.applyScalingAndRedraw(for: targetView, axis: lastScaleAxis, scaleFactor: finalScaleValue)
        } else {
            gestureDelegate?.finalizeSnap(for: targetView, usingPoints: originalPoints)
        }
        return
    }

    if recognizer.numberOfTouches < 2 {
        initialPinchLocation = recognizer.location(in: targetView)
        return
    }

    let touchPointA = recognizer.location(ofTouch: 0, in: targetView)
    let touchPointB = recognizer.location(ofTouch: 1, in: targetView)
    lastScaleAxis = detectScaleAxis(from: touchPointA, to: touchPointB, basedOn: originalPoints)

    let scaleFactor = recognizer.scale / previousScale
    let xDiff = originalPoints[1].x - originalPoints[0].x
    let yDiff = originalPoints[1].y - originalPoints[0].y

    if xDiff == 0 || yDiff == 0 {
        hasRotationApplied = false

        change detectGestureDirection(touchPointA, point2: touchPointB) {
        case "V":
            targetView.rework = targetView.rework.scaledBy(x: 1, y: scaleFactor)
        case "H":
            targetView.rework = targetView.rework.scaledBy(x: scaleFactor, y: 1)
        default:
            targetView.rework = targetView.rework.scaledBy(x: scaleFactor, y: scaleFactor)
        }

        previousScale = recognizer.scale
    } else {
        finalScaleValue = recognizer.scale
        hasRotationApplied = true
        targetView.rework = targetView.rework.scaledBy(x: scaleFactor, y: scaleFactor)
    }

    previousScale = recognizer.scale
}
func applyScalingAndRedraw(for view: UIView, axis: String, scaleFactor: CGFloat) {
    // Computes new factors primarily based on the scaling axis and applies redraw logic
    // Geometry strategies replace coordinates and redraw the rectangle accurately
}

ChatGPT helps velocity up affected person screening for medical trials – NanoApps Medical – Official web site


A brand new examine within the tutorial journal Machine Studying: Well being discovers that ChatGPT can speed up affected person screening for medical trials, exhibiting promise in decreasing delays and bettering trial success charges.

Researchers at UT Southwestern Medical Centre used ChatGPT to evaluate whether or not sufferers have been eligible to participate in medical trials and have been in a position to determine appropriate candidates inside minutes.

Scientific trials, which take a look at new drugs and procedures on the general public, are very important for creating and validating new therapies. However many trials battle to enrol sufficient members. In keeping with a current examine, as much as 20% of Nationwide Most cancers Institute (NCI)-affiliated trials fail as a result of low enrolment. This not solely inflates prices and delays outcomes, but in addition undermines the reliability of latest therapies.

At the moment, screening sufferers for trials is a guide course of. Researchers should evaluate every affected person’s medical information to find out in the event that they meet eligibility standards, which takes round 40 minutes per affected person. With restricted workers and sources, this course of is usually too sluggish to maintain up with demand.

A part of the issue is that precious affected person data contained in digital well being information (EHRs) is usually buried in unstructured textual content, similar to medical doctors’ notes, which conventional machine studying software program struggles to decipher. Consequently, many eligible sufferers are neglected as a result of there merely isn’t sufficient capability to evaluate each case. This contributes to low enrolment charges, trial delays and even cancellations, finally slowing down entry to new therapies.

To counter this downside, the researchers have checked out methods of rushing up the screening course of by utilizing ChatGPT. Researchers used GPT-3.5 and GPT-4 to analyse 74 sufferers’ information to see in the event that they certified for a head and neck most cancers trial.

3 ways of prompting the AI have been examined:

  • Structured Output (SO): asking for solutions in a set format.
  • Chain of Thought (CoT): asking the mannequin to elucidate its reasoning.
  • Self-Uncover (SD): letting the mannequin work out what to search for.

The outcomes have been promising. GPT-4 was extra correct than GPT-3.5, although barely slower and costlier. Screening occasions ranged from 1.4 to 12.4 minutes per affected person, with prices between $0.02 and $0.27.

LLMs like GPT-4 may help display sufferers for medical trials, particularly when utilizing versatile standards. They’re not good, particularly when all guidelines should be met, however they’ll save time and help human reviewers.”

Dr. Mike Dohopolski, lead writer of the examine

This analysis highlights the potential for AI to help quicker, extra environment friendly medical trials – bringing new therapies to sufferers sooner.

The examine is without doubt one of the first articles printed in IOP Publishing’s Machine Studying collection™, the world’s first open entry journal collection devoted to the appliance and growth of machine studying (ML) and synthetic intelligence (AI) for the sciences.

The identical analysis workforce have labored on a technique that permits surgeons to regulate sufferers’ radiation remedy in actual time while they’re nonetheless on the desk. Utilizing a deep studying system known as GeoDL, the AI delivers exact 3D dose estimates from CT scans and therapy information in simply 35 milliseconds. This might make adaptive radiotherapy quicker and extra environment friendly in actual medical settings.

Supply:

Journal reference:

Beattie, J., et al. (2025). ChatGPT augmented medical trial screening. Machine Studying: Well beingdoi.org/10.1088/3049-477x/adbd47.

ios – xcode run kotlin multiplatform mission can’t set up iosApp on IPhone16


Xcode run kotlin multiplatform mission to put in iosApp on IPhone16, tips on how to mounted it ?

I had obtain a clean kmp mission , and put in efficiently on IPhone16. After putting in the iOS app on an actual machine (iPhone 15), it was instantly uninstalled. Xcode reported the error proven within the screenshot.

and it may be archived to AppStore.

enter image description here

无法安装“KotlinAppProject”
Area: IXUserPresentableErrorDomain
Code: 1
Restoration Suggestion: Software is lacking the application-identifier entitlement.
Person Data: {
    DVTErrorCreationDateKey = "2025-08-03 11:32:01 +0000";
    IDERunOperationFailingWorker = IDEInstallCoreDeviceWorker;
}
--
Failed to put in the app on the machine.
Area: com.apple.dt.CoreDeviceError
Code: 3002
Person Data: {
    NSURL = "file:///Customers/HOX4SGH/Library/Developer/Xcode/DerivedData/iosApp-gbaojemzkfyvhrbgladtchqcvlfq/Construct/Merchandise/Debug-iphoneos/KotlinAppProject.app";
}
--
无法安装“KotlinAppProject”
Area: IXUserPresentableErrorDomain
Code: 1
Failure Cause: 请稍后再试。
Restoration Suggestion: Software is lacking the application-identifier entitlement.
--
Software is lacking the application-identifier entitlement.
Area: MIInstallerErrorDomain
Code: 63
Person Data: {
    FunctionName = "-[MIInstallableBundle _validateApplicationIdentifierForNewBundleSigningInfo:error:]";
    LegacyErrorString = ApplicationVerificationFailed;
    SourceFileLine = 1130;
}
--

Occasion Metadata: com.apple.dt.IDERunOperationWorkerFinished : {
    "device_identifier" = "00008140-000435313CF2801C";
    "device_isCoreDevice" = 1;
    "device_model" = "iPhone17,3";
    "device_osBuild" = "18.5 (22F76)";
    "device_platform" = "com.apple.platform.iphoneos";
    "device_thinningType" = "iPhone17,3";
    "dvt_coredevice_version" = "443.24";
    "dvt_coresimulator_version" = "1010.15";
    "dvt_mobiledevice_version" = "1784.120.3";
    "launchSession_schemeCommand" = Run;
    "launchSession_state" = 1;
    "launchSession_targetArch" = arm64;
    "operation_duration_ms" = 5069;
    "operation_errorCode" = 1;
    "operation_errorDomain" = IXUserPresentableErrorDomain;
    "operation_errorWorker" = IDEInstallCoreDeviceWorker;
    "operation_name" = IDERunOperationWorkerGroup;
    "param_debugger_attachToExtensions" = 0;
    "param_debugger_attachToXPC" = 1;
    "param_debugger_type" = 3;
    "param_destination_isProxy" = 0;
    "param_destination_platform" = "com.apple.platform.iphoneos";
    "param_diag_113575882_enable" = 0;
    "param_diag_MainThreadChecker_stopOnIssue" = 0;
    "param_diag_MallocStackLogging_enableDuringAttach" = 0;
    "param_diag_MallocStackLogging_enableForXPC" = 1;
    "param_diag_allowLocationSimulation" = 1;
    "param_diag_checker_tpc_enable" = 1;
    "param_diag_gpu_frameCapture_enable" = 0;
    "param_diag_gpu_shaderValidation_enable" = 0;
    "param_diag_gpu_validation_enable" = 0;
    "param_diag_guardMalloc_enable" = 0;
    "param_diag_memoryGraphOnResourceException" = 0;
    "param_diag_mtc_enable" = 1;
    "param_diag_queueDebugging_enable" = 1;
    "param_diag_runtimeProfile_generate" = 0;
    "param_diag_sanitizer_asan_enable" = 0;
    "param_diag_sanitizer_tsan_enable" = 0;
    "param_diag_sanitizer_tsan_stopOnIssue" = 0;
    "param_diag_sanitizer_ubsan_enable" = 0;
    "param_diag_sanitizer_ubsan_stopOnIssue" = 0;
    "param_diag_showNonLocalizedStrings" = 0;
    "param_diag_viewDebugging_enabled" = 1;
    "param_diag_viewDebugging_insertDylibOnLaunch" = 1;
    "param_install_style" = 2;
    "param_launcher_UID" = 2;
    "param_launcher_allowDeviceSensorReplayData" = 0;
    "param_launcher_kind" = 0;
    "param_launcher_style" = 99;
    "param_launcher_substyle" = 0;
    "param_runnable_appExtensionHostRunMode" = 0;
    "param_runnable_productType" = "com.apple.product-type.software";
    "param_structuredConsoleMode" = 1;
    "param_testing_launchedForTesting" = 0;
    "param_testing_suppressSimulatorApp" = 0;
    "param_testing_usingCLI" = 0;
    "sdk_canonicalName" = "iphoneos18.5";
    "sdk_osVersion" = "18.5";
    "sdk_variant" = iphoneos;
}
--


System Data

macOS Model 15.5 (Construct 24F74)
Xcode 16.4 (23792) (Construct 16F6)
Timestamp: 2025-08-03T19:32:01+08:00

Flutter iOS app UI proven on startup earlier than splash


I’m constructing an app for ios/android utilizing flutter. When the app begins, the move is:

important.dart
native splash displaying dev emblem (throughout which app begin up occurs)
authgate
app widget (extracted this from important to cut back how a lot work important was doing)
flutter splash displaying app emblem (throughout which permissions are referred to as)
touchdown display screen

The problem I’m seeing is when the iOS app begins, it briefly flashes the final seen UI display screen BEFORE the native splash display screen. e.g. If I used to be viewing the settings display screen within the app then stop the app, subsequent time I begin the app, the very first thing I see earlier than the native splash display screen is a flash of the settings display screen.

I simply wished to know if anybody has skilled that earlier than, and the way they resolved it.

I’ve googled for hours and can’t discover something related, nor can I discover something in flutter documentation. It appears I’ve managed to do one thing in begin up that created this challenge that nobody else will get? It’s driving me insane. On condition that this happens previous to native splash, I assume it’s a snapshot specific to iOS because it doesn’t occur within the android app, and whereas it’s not a breaking challenge, it doesn’t look nice from a consumer perspective.

I’ve tried including a white background to cover snapshot, as an alternative I received native splash then white display screen then native splash once more so eliminated the white background.
Added UIApplication.shared.ignoreSnapshotOnNextApplicationLaunch() to AppDelegate with no change in behaviour

1. Most important.dart

Future important() async {
  WidgetsFlutterBinding.ensureInitialized();
  tz.initializeTimeZones();
  await Future.delayed(const Period(seconds: 2));

  await Firebase.initializeApp(choices: DefaultFirebaseOptions.currentPlatform);
  await SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
  ]);

  await Hive.initFlutter();
  Hive.registerAdapter(MedicationAdapter());  // TypeID 0
  Hive.registerAdapter(DoseEntryAdapter()); // TypeID 1
  Hive.registerAdapter(HealthProfessionalAdapter()); // TypeID 2
  Hive.registerAdapter(DiscussionItemAdapter()); // TypeID 3
  Hive.registerAdapter(AppointmentAdapter()); // TypeID 4
  Hive.registerAdapter(UserProfileAdapter()); // TypeID 5
  Hive.registerAdapter(TodoItemAdapter()); // TypeID 7
  Hive.registerAdapter(DiaryEntryAdapter()); // TypeID 8
  Hive.registerAdapter(SelectedTrackersAdapter()); // TypeID 11
  Hive.registerAdapter(SelectedGoalsAdapter()); // TypeID 70
  Hive.registerAdapter(GpsActivityEntryAdapter()); // TypeID 60
  Hive.registerAdapter(WeightEntryAdapter()); // TypeID 50
  Hive.registerAdapter(WeightGoalAdapter()); // TypeID 31
  Hive.registerAdapter(ActivityGoalAdapter()); // TypeID 28
  Hive.registerAdapter(DailyGoalProgressAdapter()); // TypeID 33
  Hive.registerAdapter(WeeklyWeightEntryAdapter()); // TypeID 32
  Hive.registerAdapter(MenstrualCycleEntryAdapter()); // TypeID 43
  Hive.registerAdapter(WorkoutActivityAdapter()); // TypeID 41
  Hive.registerAdapter(MeditationGoalAdapter()); // TypeID 98
  Hive.registerAdapter(DailyMeditationProgressAdapter()); // TypeID 97

  await Hive.openBox('settings');
  await Hive.openBox('medicationAlertsSent');
  await Hive.openBox('menstrualCycleBox');
  await Hive.openBox('gps_activities');
  await Hive.openBox('gpsActivity');

  await NotificationService.initialize();
  await LocalisationSettingsService.init();

  // Load saved locale
  last savedLocaleCode = LocalisationSettingsService.getSelectedLocale();
  last initialLocale = savedLocaleCode != null ? Locale(savedLocaleCode) : null;

  runApp(
    MultiProvider(
      suppliers: [
        ChangeNotifierProvider(create: (_) => ThemeNotifier()),
        ChangeNotifierProvider(create: (_) => LocaleNotifier()..setLocale(initialLocale)),
      ],
      youngster: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({tremendous.key});

  @override
  Widget construct(BuildContext context) {
    last themeNotifier = Supplier.of(context);
    last localeNotifier = Supplier.of(context);

    return MaterialApp(
        title: 'Pebbl',
        navigatorKey: navigatorKey,
        theme: AppTheme.lightTheme,
        darkTheme: AppTheme.darkTheme,
        themeMode: themeNotifier.themeMode,
        debugShowCheckedModeBanner: false,
        residence: const NativeSplashWrapper(),
        locale: localeNotifier.locale,
        localizationsDelegates: const [
          AppLocalizations.delegate,
          GlobalMaterialLocalizations.delegate,
          GlobalCupertinoLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate,
        ],
        supportedLocales: AppLocalizations.supportedLocales,
        localeResolutionCallback: (locale, supportedLocales) {
          for (var supportedLocale in supportedLocales) {
            if (supportedLocale.languageCode == locale?.languageCode &&
                supportedLocale.countryCode == locale?.countryCode) {
              return supportedLocale;
            }
          }
          return const Locale('en', 'GB');
        }
    );
  }
}

2.  Native splash wrapper

import 'package deal:flutter/materials.dart';
import 'package deal:pebbl/screens/login/auth_gate.dart'; // ✅ Now this works

class NativeSplashWrapper extends StatefulWidget {
  const NativeSplashWrapper({tremendous.key});

  @override
  State createState() => _NativeSplashWrapperState();
}

class _NativeSplashWrapperState extends State {
  bool _ready = false;

  @override
  void initState() {
    tremendous.initState();
    _init();
  }

  Future _init() async {
    await Future.delayed(const Period(milliseconds: 100));
    if (mounted) {
      setState(() {
        _ready = true;
      });
    }
  }

  @override
  Widget construct(BuildContext context) {
    if (!_ready) {
      return const Scaffold(
        backgroundColor: Coloration(0xFFF5F5F5),
        physique: Middle(
          youngster: Picture(
            picture: AssetImage('belongings/photographs/pebbl_logo.png'),
            width: 200,
            top: 200,
          ),
        ),
      );
    }
    return const AuthGate();
  }
}
3. Pubspec.yaml (snippet for flutter native splash)

flutter_native_splash:
  colour: "#F5F5F5"
  picture: belongings/photographs/dev_logo.png
  fullscreen: true
  android: true
  ios: true

  android_12:
    picture: belongings/photographs/android12_dev_logo.png
4. Auth gate

import 'package deal:flutter/materials.dart';
import 'package deal:firebase_auth/firebase_auth.dart';
import 'package deal:pebbl/screens/login/splash_screen.dart';
import 'package deal:pebbl/screens/login/login_screen.dart';
import 'package deal:pebbl/widgets/pebbl_app.dart'; // We’ll extract PebblApp too (see beneath)

class AuthGate extends StatelessWidget {
  const AuthGate({tremendous.key});

  @override
  Widget construct(BuildContext context) {
    return StreamBuilder(
      stream: FirebaseAuth.occasion.authStateChanges(),
      builder: (context, authSnapshot) {
        last isLoggedIn = authSnapshot.hasData;
        last display screen = isLoggedIn ? SplashScreen() : const LoginScreen();
        return PebblApp(initialScreen: display screen);
      },
    );
  }
}
5. Pebbl app widget

import 'package deal:flutter/materials.dart';
import 'package deal:pebbl/screens/touchdown/landing_screen.dart';
import 'package deal:pebbl/companies/notification_service.dart';
import 'package deal:pebbl/utils/logger.dart';
import 'package deal:pebbl/important.dart'; // for navigatorKey

class PebblApp extends StatefulWidget {
  last Widget initialScreen;

  const PebblApp({tremendous.key, required this.initialScreen});

  @override
  State createState() => _PebblAppState();
}

class _PebblAppState extends State with WidgetsBindingObserver {
  @override
  void initState() {
    tremendous.initState();
    WidgetsBinding.occasion.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.occasion.removeObserver(this);
    tremendous.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      last payload = NotificationService.getAndClearPendingPayload();
      if (payload != null) {
        Logger.log('App resumed. Routing to payload: $payload');
        navigatorKey.currentState?.pushAndRemoveUntil(
          MaterialPageRoute(builder: (_) => LandingScreen(payload: payload)),
              (route) => false,
        );
      }
    }
  }

  @override
  Widget construct(BuildContext context) {
    return GestureDetector(
      onTap: () => FocusScope.of(context).unfocus(),
      youngster: widget.initialScreen,
    );
  }
}
6. Splash display screen

import 'package deal:flutter/basis.dart';
import 'package deal:pebbl/utils/logger.dart';
import 'dart:io';
import 'package deal:device_info_plus/device_info_plus.dart';
import 'package deal:flutter/materials.dart';
import 'package deal:firebase_auth/firebase_auth.dart';
import 'package deal:package_info_plus/package_info_plus.dart';
import 'package deal:pebbl/screens/touchdown/landing_screen.dart';
import 'package deal:pebbl/screens/login/login_screen.dart';
import 'package deal:permission_handler/permission_handler.dart';
import 'package deal:pebbl/companies/notification_service.dart';
import 'package deal:geolocator/geolocator.dart';

class SplashScreen extends StatefulWidget {
  last String? payloadOverride;

  const SplashScreen({tremendous.key, this.payloadOverride});

  @override
  State createState() => _SplashScreenState();
}

class _SplashScreenState extends State with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation _animation;
  String _version = '';

  @override
  void initState() {
    tremendous.initState();
    _controller = AnimationController(
      period: const Period(seconds: 2),
      vsync: this,
    )..ahead();

    _animation = CurvedAnimation(
      mother or father: _controller,
      curve: Curves.easeInOut,
    );

    _init();
  }

  Future _init() async {
    strive {
      last data = await PackageInfo.fromPlatform();
      setState(() {
        _version = 'v${data.model}+${data.buildNumber}';
      });
      if (kDebugMode) {
        Logger.log('✅ Bundle data loaded');
      }
    } catch (e) {
      if (kDebugMode) {
        Logger.log('❌ Bundle data failed: $e');
      }
      _version = 'v1.0.0';
    }

    // 🔒 iOS location permission verify
    strive {
      if (Platform.isIOS) {
        last serviceEnabled = await Geolocator.isLocationServiceEnabled();
        if (kDebugMode) {
          Logger.log('✅ [iOS] Location service enabled: $serviceEnabled');
        }
        if (serviceEnabled) {
          var permission = await Geolocator.checkPermission();
          if (kDebugMode) {
            Logger.log('✅ [iOS] Location permission standing: $permission');
          }
          if (permission == LocationPermission.denied) {
            permission = await Geolocator.requestPermission();
            if (kDebugMode) {
              Logger.log('✅ [iOS] Location permission requested: $permission');
            }
          }
        }
      }
    } catch (e) {
      if (kDebugMode) {
        Logger.log('iOS Location permission request failed: $e');
      }
    }

    // ✅ Android 13+ notification permission
    strive {
      if (Platform.isAndroid) {
        last androidInfo = await DeviceInfoPlugin().androidInfo;
        if (kDebugMode) {
          Logger.log('✅ Android SDK: ${androidInfo.model.sdkInt}');
        }
        if (androidInfo.model.sdkInt >= 33) {
          last standing = await Permission.notification.request();
          if (kDebugMode) {
            Logger.log('✅ Notification permission: $standing');
          }
          if (!standing.isGranted) {
            if (kDebugMode) {
              Logger.log('Notification permission denied');
            }
            // Optionally present a dialog right here
          }
        }
      }
    } catch (e) {
      if (kDebugMode) {
        Logger.log('Notification permission request failed: $e');
      }
    }

    // ✅ Android location permissions: foreground and background
    strive {
      if (Platform.isAndroid) {
        // ✅ 1. Test and request notification permission (Android 13+)
        last notifStatus = await Permission.notification.standing;
        if (!notifStatus.isGranted) {
          if (kDebugMode) {
            Logger.log('🔔 Requesting notification permission...');
          }
          last notifResult = await Permission.notification.request();
          if (kDebugMode) {
            Logger.log('🔔 Notification permission outcome: $notifResult');
          }
        }

        // ✅ 2. Test foreground location permission
        if (kDebugMode) {
          Logger.log('📍 Checking foreground location permission...');
        }
        var fgStatus = await Permission.location.standing;
        if (kDebugMode) {
          Logger.log('📍 Foreground location permission standing: $fgStatus');
        }

        if (!fgStatus.isGranted) {
          if (kDebugMode) {
            Logger.log('📍 Requesting foreground location permission...');
          }
          fgStatus = await Permission.location.request();
          if (kDebugMode) {
            Logger.log('📍 Foreground location permission outcome: $fgStatus');
          }
        }

        // ✅ 3. Test background location permission (if foreground granted)
        if (fgStatus.isGranted) {
          if (kDebugMode) {
            Logger.log('📍 Checking background location permission...');
          }
          var bgStatus = await Permission.locationAlways.standing;
          if (kDebugMode) {
            Logger.log('📍 Background location permission standing: $bgStatus');
          }

          if (!bgStatus.isGranted) {
            if (kDebugMode) {
              Logger.log('📍 Requesting background location permission...');
            }
            bgStatus = await Permission.locationAlways.request();
            if (kDebugMode) {
              Logger.log('📍 Background location permission outcome: $bgStatus');
            }
          } else {
            if (kDebugMode) {
              Logger.log('✅ Background location permission already granted');
            }
          }
        } else {
          if (kDebugMode) {
            Logger.log('❌ Foreground location denied, skipping background request');
          }
        }
      }
    } catch (e) {
      if (kDebugMode) {
        Logger.log('❗ Permission request failed: $e');
      }
    }


    await Future.delayed(const Period(seconds: 2));
    if (!mounted) return;

    last consumer = FirebaseAuth.occasion.currentUser;
    if (kDebugMode) {
      Logger.log('SplashScreen: currentUser = ${consumer?.uid}');
    }
    last payload = widget.payloadOverride ??
        NotificationService.getInitialPayload() ??
        NotificationService.consumePendingPayload();
    if (kDebugMode) {
      Logger.log('SplashScreen init: Firebase consumer: ${consumer?.uid}, payload: $payload');
    }
    if (kDebugMode) {
      Logger.log('SplashScreen: payload resolved = $payload');
    }

    if (consumer != null) {
      strive {
        if (kDebugMode) {
          Logger.log('Calling rescheduleAllReminders...');
        }
        await NotificationService.rescheduleAllReminders()
            .timeout(const Period(seconds: 5));
        if (kDebugMode) {
          Logger.log('rescheduleAllReminders accomplished');
        }
      } catch (e) {
        if (kDebugMode) {
          Logger.log('rescheduleAllReminders failed or timed out: $e');
        }
      }
    }

    if (!mounted) return;

    Navigator.of(context).pushReplacement(
      MaterialPageRoute(
        builder: (_) => consumer == null
            ? const LoginScreen()
            : LandingScreen(payload: payload),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    tremendous.dispose();
  }

  @override
  Widget construct(BuildContext context) {
    return Scaffold(
      backgroundColor: const Coloration(0xFFF5F5F5),
      physique: Column(
        mainAxisAlignment: MainAxisAlignment.heart,
        youngsters: [
          const Spacer(),
          FadeTransition(
            opacity: _animation,
            child: Center(
              child: Image.asset(
                'assets/images/pebbl_logo.png',
                width: 200,
                height: 200,
              ),
            ),
          ),
          const Spacer(),
          Padding(
            padding: const EdgeInsets.only(bottom: 20),
            child: Text(
              _version,
              style: const TextStyle(color: Colors.black54),
            ),
          ),
        ],
      ),
    );
  }
}
AppDelegate

import UIKit
import Flutter
import UserNotifications
import AVFAudio
import GoogleMaps // 👈 Add this import for Maps

@important
@objc class AppDelegate: FlutterAppDelegate {
  //lazy var flutterEngine = FlutterEngine(identify: "my_engine")
  override func software(
    _ software: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {

    UIApplication.shared.ignoreSnapshotOnNextApplicationLaunch()

// Begin engine (plugins auto-register when the engine is hooked up to the view)
    //flutterEngine.run()
    // Arrange audio playback class
    do {
      strive AVAudioSession.sharedInstance().setCategory(
        .playback,
        mode: .default,
        choices: [.mixWithOthers]
      )
      strive AVAudioSession.sharedInstance().setActive(true)
    } catch {
      print("Didn't set audio session class.")
    }

    // ✅ Register Google Maps API key
    GMSServices.provideAPIKey("key") // 👈 Change along with your precise key

    // Guarantee notification faucets are delivered to Flutter
    UNUserNotificationCenter.present().delegate = self

    return tremendous.software(software, didFinishLaunchingWithOptions: launchOptions)

  func applicationWillResignActive(_ software: UIApplication) {
      // Add a white view over the window earlier than backgrounding
      let whiteView = UIView(body: window?.bounds ?? .zero)
      whiteView.backgroundColor = UIColor.white
      whiteView.tag = 999 // So we are able to take away it later
      window?.addSubview(whiteView)
  }

  func applicationDidBecomeActive(_ software: UIApplication) {
      // Take away the white view when app turns into energetic
      if let whiteView = window?.viewWithTag(999) {
          whiteView.removeFromSuperview()
      }
  }
  }
}
SceneDelegate

import UIKit
import Flutter

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  var window: UIWindow?

  func scene(_ scene: UIScene,
             willConnectTo session: UISceneSession,
             choices connectionOptions: UIScene.ConnectionOptions) {

    guard let windowScene = (scene as? UIWindowScene) else { return }

    let window = UIWindow(windowScene: windowScene)

    //let appDelegate = UIApplication.shared.delegate as! AppDelegate
    //let flutterEngine = appDelegate.flutterEngine

    let flutterViewController = FlutterViewController()

    GeneratedPluginRegistrant.register(with: flutterViewController.engine)

    window.rootViewController = flutterViewController
    self.window = window
    window.makeKeyAndVisible()
  }
}

ios – Learn how to repair the gestureRecognizer over PDFKit breaking the paging within the new ios26 model?


I had mission going nice, the place i wanted to do stuff with pdfs, drawing on prime them and so on. Since apple is all closed sourced i wanted to turn out to be a bit hacky. In any case, i’ve an issue because the new ios 26 replace which breaks the behaviour. I simplified the code very a lot right into a demo mission, the place you possibly can rapidly see what’s mistaken.

When swiping left to go to the subsequent web page, it does change the web page and so on within the pdf Doc, however visually nothing occurs. I’m caught on the primary web page. I dont know what to do, tried plenty of issues, however nothing works. Anybody expert sufficient to assist me out?

import UIKit
import PDFKit
import SwiftUI

class PDFViewController: UIViewController {
    
    var pdfView: PDFView!
    var gestureHandler: GestureHandler!

    override func viewDidLoad() {
        tremendous.viewDidLoad()
        setupPDFView()
        setupGestureHandler()
        loadPDF()
    }
    
    personal func setupPDFView() {
        pdfView = PDFView(body: view.bounds)
        
        // Your actual configuration
        pdfView.autoScales = true
        pdfView.pageShadowsEnabled = false
        pdfView.backgroundColor = .white
        pdfView.displayMode = .singlePage
        
        view.addSubview(pdfView)
        
        // Setup constraints
        pdfView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            pdfView.topAnchor.constraint(equalTo: view.topAnchor),
            pdfView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            pdfView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            pdfView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
  
  personal func setupGestureHandler() {
          gestureHandler = GestureHandler(pdfView: pdfView)
          gestureHandler.setupSwipeGestures(on: view)
      }
    
    personal func loadPDF() {
        if let path = Bundle.principal.path(forResource: "sonate12", ofType: "pdf"),
           let doc = PDFDocument(url: URL(fileURLWithPath: path)) {
            pdfView.doc = doc
        } else {
            print("Couldn't discover sonate12.pdf in bundle")
        }
    }
}


class GestureHandler {
    
    personal weak var pdfView: PDFView?
    
    init(pdfView: PDFView) {
        self.pdfView = pdfView
    }
    
    func setupSwipeGestures(on view: UIView) {
        // Left swipe - go to subsequent web page
        let leftSwipe = UISwipeGestureRecognizer(goal: self, motion: #selector(handleSwipe(_:)))
        leftSwipe.route = .left
        view.addGestureRecognizer(leftSwipe)
        
        // Proper swipe - go to earlier web page
        let rightSwipe = UISwipeGestureRecognizer(goal: self, motion: #selector(handleSwipe(_:)))
        rightSwipe.route = .proper
        view.addGestureRecognizer(rightSwipe)
    }
    
    @objc personal func handleSwipe(_ gesture: UISwipeGestureRecognizer) {
        guard let pdfView = pdfView,
              let doc = pdfView.doc,
              let currentPage = pdfView.currentPage else {
            print("🚫 No PDF view, doc, or present web page out there")
            return
        }
        
        let currentIndex = doc.index(for: currentPage)
        let totalPages = doc.pageCount
        
        print("📄 Present state: Web page (currentIndex + 1) of (totalPages)")
        print("👆 Swipe route: (gesture.route == .left ? "LEFT (subsequent)" : "RIGHT (earlier)")")
        
        swap gesture.route {
        case .left:
            // Subsequent web page
            guard currentIndex < doc.pageCount - 1 else {
                print("🚫 Already on final web page ((currentIndex + 1)), can not go ahead")
                return
            }
            
            let nextPage = doc.web page(at: currentIndex + 1)
            if let web page = nextPage {
                print("➡️ Going to web page (currentIndex + 2)")
                pdfView.go(to: web page)
              pdfView.setNeedsDisplay()
              pdfView.layoutIfNeeded()
                // Test if navigation truly labored
                DispatchQueue.principal.asyncAfter(deadline: .now() + 0.1) {
                    if let newCurrentPage = pdfView.currentPage {
                        let newIndex = doc.index(for: newCurrentPage)
                        print("✅ Navigation consequence: Now on web page (newIndex + 1)")
                        if newIndex == currentIndex {
                            print("⚠️ WARNING: Web page did not change visually!")
                        }
                    }
                }
            } else {
                print("🚫 Couldn't get subsequent web page object")
            }
            
        case .proper:
            // Earlier web page
            guard currentIndex > 0 else {
                print("🚫 Already on first web page (1), can not return")
                return
            }
            
            let previousPage = doc.web page(at: currentIndex - 1)
            if let web page = previousPage {
                print("⬅️ Going to web page (currentIndex)")
                pdfView.go(to: web page)
              pdfView.setNeedsDisplay()
              pdfView.layoutIfNeeded()
              let bounds = pdfView.bounds
              pdfView.bounds = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.width + 0.01, peak: bounds.peak)
              pdfView.bounds = bounds

                
                // Test if navigation truly labored
                DispatchQueue.principal.asyncAfter(deadline: .now() + 0.1) {
                    if let newCurrentPage = pdfView.currentPage {
                        let newIndex = doc.index(for: newCurrentPage)
                        print("✅ Navigation consequence: Now on web page (newIndex + 1)")
                        if newIndex == currentIndex {
                            print("⚠️ WARNING: Web page did not change visually!")
                        }
                    }
                }
            } else {
                print("🚫 Couldn't get earlier web page object")
            }
            
        default:
            print("🤷‍♂️ Unknown swipe route")
            break
        }
    }
}


struct PDFViewerRepresentable: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> PDFViewController {
        return PDFViewController()
    }
    
    func updateUIViewController(_ uiViewController: PDFViewController, context: Context) {
        // No updates wanted
    }
}

You may take a look at the code right here as effectively: https://github.com/vallezw/swift-bug-ios26