diff --git a/LNPCSwiftRefinements/SwiftRefinements.swift b/LNPCSwiftRefinements/SwiftRefinements.swift
index b6f343d..7c071ef 100644
--- a/LNPCSwiftRefinements/SwiftRefinements.swift
+++ b/LNPCSwiftRefinements/SwiftRefinements.swift
@@ -2,8 +2,8 @@
// SwiftRefinements.swift
// LNPopupController
//
-// Created by Leo Natan on 8/2/21.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-08-02.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
import UIKit
@@ -11,6 +11,48 @@ import UIKit
@_exported import LNPopupController_ObjC
#endif
+#if canImport(SwiftUI)
+import SwiftUI
+
+@available(iOS 13, *)
+@_cdecl("__ln_doNotCall__fixUIHostingViewHitTest")
+@_spi(LNPopupControllerInternal)
+public
+func __ln_doNotCall__fixUIHostingViewHitTest() {
+ DispatchQueue.main.async {
+ guard let view = UIHostingController(rootView: EmptyView()).view else {
+ return
+ }
+
+ let cls = type(of: view)
+ let sel = #selector(UIView.hitTest(_:with:))
+ let method = class_getInstanceMethod(cls, sel)!
+
+ let _orig: @convention(c) (_ self: UIView, _ sel: Selector, _ point: CGPoint, _ event: UIEvent?) -> UIView?
+ _orig = unsafeBitCast(method_getImplementation(method), to: type(of: _orig))
+
+ let orig: (UIView, CGPoint, UIEvent?) -> UIView? = { _self, point, event in
+ _orig(_self, sel, point, event)
+ }
+
+ let impl: @convention(block) (UIView, CGPoint, UIEvent?) -> UIView? = { _self, point, event in
+ if let popupContentView = _self.subviews.filter({ $0 is LNPopupContentView }).first, popupContentView.point(inside: popupContentView.convert(point, from: _self), with: event), let popupContentViewHitTest = popupContentView.hitTest(popupContentView.convert(point, from: _self), with: event) {
+ return popupContentViewHitTest
+ }
+
+ if let popupBar = _self.subviews.filter({ $0 is LNPopupBar }).first, popupBar.point(inside: popupBar.convert(point, from: _self), with: event), let popupBarHitTest = popupBar.hitTest(popupBar.convert(point, from: _self), with: event) {
+ return popupBarHitTest
+ }
+
+ return orig(_self, point, event)
+ }
+
+ method_setImplementation(method, imp_implementationWithBlock(impl))
+ }
+}
+#endif
+
+public
extension Double {
/// The default popup snap percent. See `LNPopupInteractionStyle.customizedSnap(percent:)` for more information.
static var defaultPopupSnapPercent: Double {
@@ -18,7 +60,8 @@ extension Double {
}
}
-public extension UIViewController {
+public
+extension UIViewController {
/// Available interaction styles with the popup bar and popup content view.
enum PopupInteractionStyle {
/// The default interaction style for the current environment.
@@ -35,26 +78,30 @@ public extension UIViewController {
/// No interaction.
case none
}
-
+
/// The popup bar interaction style.
var popupInteractionStyle: PopupInteractionStyle {
get {
switch __popupInteractionStyle {
- case .none:
- return .none
+ case .default:
+ return .default
case .drag:
return .drag
case .snap:
return __popupSnapPercent == .defaultPopupSnapPercent ? .snap : .customizedSnap(percent: __popupSnapPercent)
- default:
- return .default
+ case .scroll:
+ return .scroll
+ case .none:
+ return .none
+ @unknown default:
+ fatalError("Please open an issue here: https://github.com/LeoNatan/LNPopupController/issues/new/choose")
}
}
set {
switch newValue {
- case .none:
- __popupInteractionStyle = .none
+ case .default:
+ __popupInteractionStyle = .default
return
case .drag:
__popupInteractionStyle = .drag
@@ -67,15 +114,36 @@ public extension UIViewController {
__popupInteractionStyle = .snap
__popupSnapPercent = percent
return
- default:
- __popupInteractionStyle = .default
+ case .scroll:
+ __popupInteractionStyle = .scroll
+ return
+ case .none:
+ __popupInteractionStyle = .none
return
}
}
}
+
+ var effectivePopupInteractionStyle: PopupInteractionStyle {
+ switch __effectivePopupInteractionStyle {
+ case .drag:
+ return .drag
+ case .snap:
+ return __popupSnapPercent == .defaultPopupSnapPercent ? .snap : .customizedSnap(percent: __popupSnapPercent)
+ case .scroll:
+ return .scroll
+ case .none:
+ return .none
+ case .default:
+ fallthrough
+ @unknown default:
+ fatalError("Please open an issue here: https://github.com/LeoNatan/LNPopupController/issues/new/choose")
+ }
+ }
}
-public extension LNPopupItem {
+public
+extension LNPopupItem {
/// The popup item's attributed title.
///
/// If no title or subtitle is set, the system will use the view controller's title.
@@ -101,8 +169,9 @@ public extension LNPopupItem {
}
}
-@available(iOS 13.0, *)
-public extension LNPopupBarAppearance {
+@available(iOS 13, *)
+public
+extension LNPopupBarAppearance {
/// Display attributes for the popup bar’s title text.
///
/// Only attributes from the UIKit scope are supported.
@@ -129,3 +198,59 @@ public extension LNPopupBarAppearance {
}
}
}
+
+public
+extension UIViewController {
+ /// Presents an interactive popup bar in the receiver's view hierarchy and optionally opens the popup in the same animation. The popup bar is attached to the receiver's docking view.
+ ///
+ /// You may call this method multiple times with different controllers, triggering replacement to the popup content view and update to the popup bar, if popup is open or bar presented, respectively.
+ ///
+ /// The provided controller is retained by the system and will be released once a different controller is presented or when the popup bar is dismissed.
+ /// - Parameters:
+ /// - contentViewController: The controller for popup presentation.
+ /// - openPopup: Pass `true` to open the popup in the same animation; otherwise, pass `false`.
+ /// - animated: Pass `true` to animate the presentation; otherwise, pass `false`.
+ /// - completion: The block to execute after the presentation finishes. This block has no return value and takes no parameters. You may specify `nil` for this parameter.
+ func presentPopupBar(with contentViewController: UIViewController, openPopup: Bool = false, animated: Bool, completion: (() -> Void)? = nil) {
+ __presentPopupBar(withContentViewController: contentViewController, openPopup: openPopup, animated: animated, completion: completion)
+ }
+
+ /// Presents an interactive popup bar in the receiver's view hierarchy and optionally opens the popup in the same animation. The popup bar is attached to the receiver's docking view.
+ ///
+ /// You may call this method multiple times with different controllers, triggering replacement to the popup content view and update to the popup bar, if popup is open or bar presented, respectively.
+ ///
+ /// The provided controller is retained by the system and will be released once a different controller is presented or when the popup bar is dismissed.
+ /// - Parameters:
+ /// - contentViewController: The controller for popup presentation.
+ /// - openPopup: Pass `true` to open the popup in the same animation; otherwise, pass `false`.
+ /// - animated: Pass `true` to animate the presentation; otherwise, pass `false`.
+ /// - completion: The block to execute after the presentation finishes. This block has no return value and takes no parameters. You may specify `nil` for this parameter.
+ @available(*, deprecated, message: "Use presentPopupBar(with:openPopup:animated:completion:) instead.")
+ func presentPopupBar(withContentViewController contentViewController: UIViewController, openPopup: Bool = false, animated: Bool, completion: (() -> Void)? = nil) {
+ __presentPopupBar(withContentViewController: contentViewController, openPopup: openPopup, animated: animated, completion: completion)
+ }
+
+ /// Opens the popup, displaying the content view controller's view.
+ /// - Parameters:
+ /// - animated: Pass `true` to animate; otherwise, pass `false`.
+ /// - completion: The block to execute after the popup is opened. This block has no return value and takes no parameters. You may specify `nil` for this parameter.
+ func openPopup(animated: Bool, completion: (() -> Void)? = nil) {
+ __openPopup(animated: animated, completion: completion)
+ }
+
+ /// Closes the popup, hiding the content view controller's view.
+ /// - Parameters:
+ /// - animated: Pass `true` to animate; otherwise, pass `false`.
+ /// - completion: The block to execute after the popup is closed. This block has no return value and takes no parameters. You may specify `nil` for this parameter.
+ func closePopup(animated: Bool, completion: (() -> Void)? = nil) {
+ __closePopup(animated: animated, completion: completion)
+ }
+
+ /// Dismisses the popup presentation, closing the popup if open and dismissing the popup bar.
+ /// - Parameters:
+ /// - animated: Pass `true` to animate; otherwise, pass `false`.
+ /// - completion: The block to execute after the dismissal. This block has no return value and takes no parameters. You may specify `nil` for this parameter.
+ func dismissPopupBar(animated: Bool, completion: (() -> Void)? = nil) {
+ __dismissPopupBar(animated: animated, completion: completion)
+ }
+}
diff --git a/LNPopupController/Info.plist b/LNPopupController/Info.plist
index 3222b32..944c736 100644
--- a/LNPopupController/Info.plist
+++ b/LNPopupController/Info.plist
@@ -9,7 +9,7 @@
CFBundlePackageType
FMWK
CFBundleShortVersionString
- 2.18.2
+ 3.0.6
CFBundleVersion
1
LSRequiresIPhoneOS
diff --git a/LNPopupController/LNPopupController.h b/LNPopupController/LNPopupController.h
index 71d40be..41a1cee 100644
--- a/LNPopupController/LNPopupController.h
+++ b/LNPopupController/LNPopupController.h
@@ -2,8 +2,8 @@
// LNPopupController.h
// LNPopupController
//
-// Created by Leo Natan on 7/17/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController.xcodeproj/project.pbxproj b/LNPopupController/LNPopupController.xcodeproj/project.pbxproj
index 1ef2dd1..b64a61b 100644
--- a/LNPopupController/LNPopupController.xcodeproj/project.pbxproj
+++ b/LNPopupController/LNPopupController.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
@@ -13,37 +13,37 @@
390DD0111BB2EAC30064DB4A /* LNPopupContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 390DD0101BB2EAC30064DB4A /* LNPopupContentView.h */; settings = {ATTRIBUTES = (Public, ); }; };
391481BA1DCFA514002416D1 /* LNChevronView.m in Sources */ = {isa = PBXBuildFile; fileRef = 391481B81DCFA514002416D1 /* LNChevronView.m */; };
391481BB1DCFA514002416D1 /* LNChevronView.h in Headers */ = {isa = PBXBuildFile; fileRef = 391481B91DCFA514002416D1 /* LNChevronView.h */; settings = {ATTRIBUTES = (Private, ); }; };
+ 392200702C663A03008AFD36 /* _LNPopupAddressInfo.h in Sources */ = {isa = PBXBuildFile; fileRef = 3922006F2C663A03008AFD36 /* _LNPopupAddressInfo.h */; };
39222ADB1F1A1C5800388E06 /* LNPopupOpenTapGestureRecognizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 39222AD91F1A1C5800388E06 /* LNPopupOpenTapGestureRecognizer.h */; settings = {ATTRIBUTES = (Private, ); }; };
39222ADC1F1A1C5800388E06 /* LNPopupOpenTapGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 39222ADA1F1A1C5800388E06 /* LNPopupOpenTapGestureRecognizer.m */; };
39314A521B6AE7A400574D3C /* MarqueeLabel.h in Headers */ = {isa = PBXBuildFile; fileRef = 39314A501B6AE7A400574D3C /* MarqueeLabel.h */; settings = {ATTRIBUTES = (Private, ); }; };
39314A531B6AE7A400574D3C /* MarqueeLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 39314A511B6AE7A400574D3C /* MarqueeLabel.m */; };
- 393E4EA72670F12500929E47 /* LNPopupBarAppearance.m in Sources */ = {isa = PBXBuildFile; fileRef = 393E4EA52670F12500929E47 /* LNPopupBarAppearance.m */; };
+ 393E4EA72670F12500929E47 /* LNPopupBarAppearance.mm in Sources */ = {isa = PBXBuildFile; fileRef = 393E4EA52670F12500929E47 /* LNPopupBarAppearance.mm */; };
393E4EAA2670F2D000929E47 /* LNPopupBarAppearance.h in Headers */ = {isa = PBXBuildFile; fileRef = 393E4EA82670F2D000929E47 /* LNPopupBarAppearance.h */; settings = {ATTRIBUTES = (Public, ); }; };
394005FC2AC3C60900291421 /* _LNPopupBarBackgroundMaskView.h in Headers */ = {isa = PBXBuildFile; fileRef = 394005FA2AC3C60900291421 /* _LNPopupBarBackgroundMaskView.h */; };
394005FD2AC3C60900291421 /* _LNPopupBarBackgroundMaskView.m in Sources */ = {isa = PBXBuildFile; fileRef = 394005FB2AC3C60900291421 /* _LNPopupBarBackgroundMaskView.m */; };
3947E1A11B61CD1F0001178B /* UIViewController+LNPopupSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 3947E19F1B61CD1F0001178B /* UIViewController+LNPopupSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 3947E1A21B61CD1F0001178B /* UIViewController+LNPopupSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 3947E1A01B61CD1F0001178B /* UIViewController+LNPopupSupport.m */; };
- 3947E1A61B61CD650001178B /* LNPopupBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 3947E1A41B61CD650001178B /* LNPopupBar.m */; };
+ 3947E1A21B61CD1F0001178B /* UIViewController+LNPopupSupport.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3947E1A01B61CD1F0001178B /* UIViewController+LNPopupSupport.mm */; };
+ 3947E1A61B61CD650001178B /* LNPopupBar.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3947E1A41B61CD650001178B /* LNPopupBar.mm */; };
3947E1A91B61CDA40001178B /* LNPopupCloseButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 3947E1A71B61CDA40001178B /* LNPopupCloseButton.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 3947E1AA1B61CDA40001178B /* LNPopupCloseButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 3947E1A81B61CDA40001178B /* LNPopupCloseButton.m */; };
+ 3947E1AA1B61CDA40001178B /* LNPopupCloseButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3947E1A81B61CDA40001178B /* LNPopupCloseButton.mm */; };
3947E1B21B61CE4A0001178B /* LNPopupController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3947E1B01B61CE4A0001178B /* LNPopupController.h */; settings = {ATTRIBUTES = (Private, ); }; };
- 3947E1B31B61CE4A0001178B /* LNPopupController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3947E1B11B61CE4A0001178B /* LNPopupController.m */; };
+ 3947E1B31B61CE4A0001178B /* LNPopupController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3947E1B11B61CE4A0001178B /* LNPopupController.mm */; };
3947E1BA1B6300370001178B /* LNPopupBar+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 3947E1B81B6300370001178B /* LNPopupBar+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
394A85B61B630409004FFC61 /* LNPopupBar.h in Headers */ = {isa = PBXBuildFile; fileRef = 394A85B51B630409004FFC61 /* LNPopupBar.h */; settings = {ATTRIBUTES = (Public, ); }; };
394A85BA1B6304F5004FFC61 /* LNPopupItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 394A85B81B6304F5004FFC61 /* LNPopupItem.m */; };
394A85BD1B6306AE004FFC61 /* LNPopupItem+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 394A85BB1B6306AE004FFC61 /* LNPopupItem+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
394A85C11B630992004FFC61 /* _LNWeakRef.h in Headers */ = {isa = PBXBuildFile; fileRef = 394A85BF1B630992004FFC61 /* _LNWeakRef.h */; };
394A85C21B630992004FFC61 /* _LNWeakRef.m in Sources */ = {isa = PBXBuildFile; fileRef = 394A85C01B630992004FFC61 /* _LNWeakRef.m */; };
- 394A85C91B63FE52004FFC61 /* UIViewController+LNPopupSupportPrivate.m in Sources */ = {isa = PBXBuildFile; fileRef = 394A85C81B63FE52004FFC61 /* UIViewController+LNPopupSupportPrivate.m */; };
- 39500DFF2B4DD02300E20D08 /* _LNPopupBarAppearanceLegacySupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 39500DFD2B4DD02300E20D08 /* _LNPopupBarAppearanceLegacySupport.h */; };
- 39500E002B4DD02300E20D08 /* _LNPopupBarAppearanceLegacySupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 39500DFE2B4DD02300E20D08 /* _LNPopupBarAppearanceLegacySupport.m */; };
+ 394A85C91B63FE52004FFC61 /* UIViewController+LNPopupSupportPrivate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 394A85C81B63FE52004FFC61 /* UIViewController+LNPopupSupportPrivate.mm */; };
39599BAA1E02CD65008EE386 /* LNPopupCustomBarViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 39599BA81E02CD65008EE386 /* LNPopupCustomBarViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
39599BAB1E02CD65008EE386 /* LNPopupCustomBarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 39599BA91E02CD65008EE386 /* LNPopupCustomBarViewController.m */; };
39599BAD1E02CDF4008EE386 /* LNPopupCustomBarViewController+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 39599BAC1E02CDF4008EE386 /* LNPopupCustomBarViewController+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
396A8DE826BEA36B005914B0 /* LNPopupBarAppearanceChainProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 396A8DE626BEA36B005914B0 /* LNPopupBarAppearanceChainProxy.h */; };
396A8DE926BEA36B005914B0 /* LNPopupBarAppearanceChainProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 396A8DE726BEA36B005914B0 /* LNPopupBarAppearanceChainProxy.m */; };
396D62722610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 396D62702610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.h */; };
- 396D62732610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.m in Sources */ = {isa = PBXBuildFile; fileRef = 396D62712610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.m */; };
+ 396D62732610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 396D62712610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.mm */; };
+ 3975A5C42C663B520027FCDD /* _LNPopupAddressInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = 392200712C663A23008AFD36 /* _LNPopupAddressInfo.mm */; };
397AFBFB1F1A1ED200E7D95C /* LNForwardingDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 397AFBF91F1A1ED200E7D95C /* LNForwardingDelegate.h */; settings = {ATTRIBUTES = (Private, ); }; };
397AFBFC1F1A1ED200E7D95C /* LNForwardingDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 397AFBFA1F1A1ED200E7D95C /* LNForwardingDelegate.m */; };
397AFC011F1A21DD00E7D95C /* LNPopupLongPressGestureRecognizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 397AFBFF1F1A21DD00E7D95C /* LNPopupLongPressGestureRecognizer.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -56,21 +56,43 @@
398C2902260CCDA6000690FB /* LNPopupCloseButton+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 39109DA11DD8A305004B5FAB /* LNPopupCloseButton+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
398C2903260CCDA6000690FB /* LNPopupContentView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 3907CDB924D9BD8A007C9300 /* LNPopupContentView+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
399BA1D42A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 399BA1D22A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.h */; };
- 399BA1D52A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 399BA1D32A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.m */; };
- 399D37B82ADC672D00EA5038 /* _LNPopupBarShadowedImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = 399D37B62ADC672D00EA5038 /* _LNPopupBarShadowedImageView.h */; };
- 399D37B92ADC672D00EA5038 /* _LNPopupBarShadowedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 399D37B72ADC672D00EA5038 /* _LNPopupBarShadowedImageView.m */; };
+ 399BA1D52A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.mm in Sources */ = {isa = PBXBuildFile; fileRef = 399BA1D32A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.mm */; };
+ 399D37B82ADC672D00EA5038 /* LNPopupImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = 399D37B62ADC672D00EA5038 /* LNPopupImageView.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 399D37B92ADC672D00EA5038 /* LNPopupImageView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 399D37B72ADC672D00EA5038 /* LNPopupImageView.mm */; };
39A0DDB326F7894700E5D751 /* NSAttributedString+LNPopupSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 39A0DDB126F7894700E5D751 /* NSAttributedString+LNPopupSupport.h */; };
39A0DDB426F7894700E5D751 /* NSAttributedString+LNPopupSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 39A0DDB226F7894700E5D751 /* NSAttributedString+LNPopupSupport.m */; };
39A9249A1B58530A003C1C19 /* LNPopupController.h in Headers */ = {isa = PBXBuildFile; fileRef = 39A924991B5852ED003C1C19 /* LNPopupController.h */; settings = {ATTRIBUTES = (Public, ); }; };
39AC479126C69ACA001D53C2 /* LNMath.h in Headers */ = {isa = PBXBuildFile; fileRef = 39AC478F26C69AC9001D53C2 /* LNMath.h */; };
39AC479226C69ACA001D53C2 /* LNMath.m in Sources */ = {isa = PBXBuildFile; fileRef = 39AC479026C69AC9001D53C2 /* LNMath.m */; };
+ 39AF84EE2DA12D2D00E59C44 /* _LNPopupBarAppearanceLegacySupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 39AF84ED2DA12D2D00E59C44 /* _LNPopupBarAppearanceLegacySupport.m */; };
+ 39AF84EF2DA12D2D00E59C44 /* _LNPopupBarAppearanceLegacySupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 39AF84EC2DA12D2D00E59C44 /* _LNPopupBarAppearanceLegacySupport.h */; };
39B3EB0D24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 39B3EB0B24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; };
- 39B3EB0E24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.m in Sources */ = {isa = PBXBuildFile; fileRef = 39B3EB0C24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.m */; };
+ 39B3EB0E24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39B3EB0C24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.mm */; };
+ 39B5500D2D92D29000B474EC /* _LNPopupTransitionAnimator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39B550002D92D29000B474EC /* _LNPopupTransitionAnimator.mm */; };
+ 39B5500E2D92D29000B474EC /* _LNPopupTransitionCloseAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = 39B550022D92D29000B474EC /* _LNPopupTransitionCloseAnimator.m */; };
+ 39B5500F2D92D29000B474EC /* _LNPopupTransitionOpenAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = 39B550082D92D29000B474EC /* _LNPopupTransitionOpenAnimator.m */; };
+ 39B550102D92D29000B474EC /* _LNPopupTransitionPreferredOpenAnimator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39B5500C2D92D29000B474EC /* _LNPopupTransitionPreferredOpenAnimator.mm */; };
+ 39B550112D92D29000B474EC /* _LNPopupTransitionPreferredCloseAnimator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39B5500A2D92D29000B474EC /* _LNPopupTransitionPreferredCloseAnimator.mm */; };
+ 39B550122D92D29000B474EC /* _LNPopupTransitionGenericOpenAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = 39B550062D92D29000B474EC /* _LNPopupTransitionGenericOpenAnimator.m */; };
+ 39B550132D92D29000B474EC /* _LNPopupTransitionGenericCloseAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = 39B550042D92D29000B474EC /* _LNPopupTransitionGenericCloseAnimator.m */; };
+ 39B550142D92D29000B474EC /* _LNPopupTransitionPreferredOpenAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = 39B5500B2D92D29000B474EC /* _LNPopupTransitionPreferredOpenAnimator.h */; };
+ 39B550152D92D29000B474EC /* _LNPopupTransitionOpenAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = 39B550072D92D29000B474EC /* _LNPopupTransitionOpenAnimator.h */; };
+ 39B550162D92D29000B474EC /* _LNPopupTransitionCloseAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = 39B550012D92D29000B474EC /* _LNPopupTransitionCloseAnimator.h */; };
+ 39B550172D92D29000B474EC /* _LNPopupTransitionAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = 39B54FFF2D92D29000B474EC /* _LNPopupTransitionAnimator.h */; };
+ 39B550182D92D29000B474EC /* _LNPopupTransitionGenericCloseAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = 39B550032D92D29000B474EC /* _LNPopupTransitionGenericCloseAnimator.h */; };
+ 39B550192D92D29000B474EC /* _LNPopupTransitionPreferredCloseAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = 39B550092D92D29000B474EC /* _LNPopupTransitionPreferredCloseAnimator.h */; };
+ 39B5501A2D92D29000B474EC /* _LNPopupTransitionGenericOpenAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = 39B550052D92D29000B474EC /* _LNPopupTransitionGenericOpenAnimator.h */; };
+ 39C553DF2D8F2B3200F8D8B2 /* _LNPopupTransitionView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39C553DE2D8F2B3200F8D8B2 /* _LNPopupTransitionView.mm */; };
+ 39C553E02D8F2B3200F8D8B2 /* _LNPopupTransitionView.h in Headers */ = {isa = PBXBuildFile; fileRef = 39C553DD2D8F2B3200F8D8B2 /* _LNPopupTransitionView.h */; };
+ 39C553E32D91925300F8D8B2 /* LNPopupImageView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 39C553E22D91924900F8D8B2 /* LNPopupImageView+Private.h */; };
39C81A321B642DDD00D3B645 /* LNPopupItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 394A85B71B6304F5004FFC61 /* LNPopupItem.h */; settings = {ATTRIBUTES = (Public, ); }; };
39E7A604200B5157007AF3AD /* _LNPopupSwizzlingUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 39E7A602200B5157007AF3AD /* _LNPopupSwizzlingUtils.h */; };
39E7A605200B5157007AF3AD /* _LNPopupSwizzlingUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 39E7A603200B5157007AF3AD /* _LNPopupSwizzlingUtils.m */; };
39E9FE11275B984B00A47D61 /* LNPopupDefinitions.h in Headers */ = {isa = PBXBuildFile; fileRef = 39E9FE10275B97CC00A47D61 /* LNPopupDefinitions.h */; settings = {ATTRIBUTES = (Public, ); }; };
39F8228826B80B6F0070DDBA /* SwiftRefinements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39F8228726B80B6F0070DDBA /* SwiftRefinements.swift */; };
+ 39F9DECA2C84A211005CA5FB /* _LNPopupBase64Utils.hh in Headers */ = {isa = PBXBuildFile; fileRef = 39F9DEC92C84A207005CA5FB /* _LNPopupBase64Utils.hh */; };
+ 39FA8FD12D9F84000068FD0D /* LNPopupDebug.h in Headers */ = {isa = PBXBuildFile; fileRef = 39FA8FCF2D9F84000068FD0D /* LNPopupDebug.h */; };
+ 39FA8FD22D9F84000068FD0D /* LNPopupDebug.m in Sources */ = {isa = PBXBuildFile; fileRef = 39FA8FD02D9F84000068FD0D /* LNPopupDebug.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -82,22 +104,24 @@
39109DA11DD8A305004B5FAB /* LNPopupCloseButton+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LNPopupCloseButton+Private.h"; sourceTree = ""; };
391481B81DCFA514002416D1 /* LNChevronView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNChevronView.m; sourceTree = ""; };
391481B91DCFA514002416D1 /* LNChevronView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNChevronView.h; sourceTree = ""; };
+ 3922006F2C663A03008AFD36 /* _LNPopupAddressInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupAddressInfo.h; sourceTree = ""; };
+ 392200712C663A23008AFD36 /* _LNPopupAddressInfo.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _LNPopupAddressInfo.mm; sourceTree = ""; };
39222AD91F1A1C5800388E06 /* LNPopupOpenTapGestureRecognizer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LNPopupOpenTapGestureRecognizer.h; sourceTree = ""; };
39222ADA1F1A1C5800388E06 /* LNPopupOpenTapGestureRecognizer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LNPopupOpenTapGestureRecognizer.m; sourceTree = ""; };
39314A501B6AE7A400574D3C /* MarqueeLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MarqueeLabel.h; sourceTree = ""; };
39314A511B6AE7A400574D3C /* MarqueeLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MarqueeLabel.m; sourceTree = ""; };
- 393E4EA52670F12500929E47 /* LNPopupBarAppearance.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LNPopupBarAppearance.m; sourceTree = ""; };
+ 393E4EA52670F12500929E47 /* LNPopupBarAppearance.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LNPopupBarAppearance.mm; sourceTree = ""; };
393E4EA82670F2D000929E47 /* LNPopupBarAppearance.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LNPopupBarAppearance.h; sourceTree = ""; };
393E4EAC26713E5700929E47 /* LNPopupBarAppearance+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LNPopupBarAppearance+Private.h"; sourceTree = ""; };
394005FA2AC3C60900291421 /* _LNPopupBarBackgroundMaskView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupBarBackgroundMaskView.h; sourceTree = ""; };
394005FB2AC3C60900291421 /* _LNPopupBarBackgroundMaskView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _LNPopupBarBackgroundMaskView.m; sourceTree = ""; };
3947E19F1B61CD1F0001178B /* UIViewController+LNPopupSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "UIViewController+LNPopupSupport.h"; sourceTree = ""; };
- 3947E1A01B61CD1F0001178B /* UIViewController+LNPopupSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = "UIViewController+LNPopupSupport.m"; sourceTree = ""; };
- 3947E1A41B61CD650001178B /* LNPopupBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNPopupBar.m; sourceTree = ""; };
+ 3947E1A01B61CD1F0001178B /* UIViewController+LNPopupSupport.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = "UIViewController+LNPopupSupport.mm"; sourceTree = ""; };
+ 3947E1A41B61CD650001178B /* LNPopupBar.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = LNPopupBar.mm; sourceTree = ""; };
3947E1A71B61CDA40001178B /* LNPopupCloseButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNPopupCloseButton.h; sourceTree = ""; };
- 3947E1A81B61CDA40001178B /* LNPopupCloseButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNPopupCloseButton.m; sourceTree = ""; };
+ 3947E1A81B61CDA40001178B /* LNPopupCloseButton.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = LNPopupCloseButton.mm; sourceTree = ""; };
3947E1B01B61CE4A0001178B /* LNPopupController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNPopupController.h; sourceTree = ""; };
- 3947E1B11B61CE4A0001178B /* LNPopupController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = LNPopupController.m; sourceTree = ""; };
+ 3947E1B11B61CE4A0001178B /* LNPopupController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = LNPopupController.mm; sourceTree = ""; };
3947E1B81B6300370001178B /* LNPopupBar+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "LNPopupBar+Private.h"; sourceTree = ""; };
394A85B51B630409004FFC61 /* LNPopupBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNPopupBar.h; sourceTree = ""; };
394A85B71B6304F5004FFC61 /* LNPopupItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNPopupItem.h; sourceTree = ""; };
@@ -106,16 +130,14 @@
394A85BF1B630992004FFC61 /* _LNWeakRef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _LNWeakRef.h; sourceTree = ""; };
394A85C01B630992004FFC61 /* _LNWeakRef.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _LNWeakRef.m; sourceTree = ""; };
394A85C71B63FB96004FFC61 /* UIViewController+LNPopupSupportPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+LNPopupSupportPrivate.h"; sourceTree = ""; };
- 394A85C81B63FE52004FFC61 /* UIViewController+LNPopupSupportPrivate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = "UIViewController+LNPopupSupportPrivate.m"; sourceTree = ""; };
- 39500DFD2B4DD02300E20D08 /* _LNPopupBarAppearanceLegacySupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupBarAppearanceLegacySupport.h; sourceTree = ""; };
- 39500DFE2B4DD02300E20D08 /* _LNPopupBarAppearanceLegacySupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _LNPopupBarAppearanceLegacySupport.m; sourceTree = ""; };
+ 394A85C81B63FE52004FFC61 /* UIViewController+LNPopupSupportPrivate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = "UIViewController+LNPopupSupportPrivate.mm"; sourceTree = ""; };
39599BA81E02CD65008EE386 /* LNPopupCustomBarViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNPopupCustomBarViewController.h; sourceTree = ""; };
39599BA91E02CD65008EE386 /* LNPopupCustomBarViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNPopupCustomBarViewController.m; sourceTree = ""; };
39599BAC1E02CDF4008EE386 /* LNPopupCustomBarViewController+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "LNPopupCustomBarViewController+Private.h"; sourceTree = ""; };
396A8DE626BEA36B005914B0 /* LNPopupBarAppearanceChainProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LNPopupBarAppearanceChainProxy.h; sourceTree = ""; };
396A8DE726BEA36B005914B0 /* LNPopupBarAppearanceChainProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LNPopupBarAppearanceChainProxy.m; sourceTree = ""; };
396D62702610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIContextMenuInteraction+LNPopupSupportPrivate.h"; sourceTree = ""; };
- 396D62712610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIContextMenuInteraction+LNPopupSupportPrivate.m"; sourceTree = ""; };
+ 396D62712610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIContextMenuInteraction+LNPopupSupportPrivate.mm"; sourceTree = ""; };
397AFBF91F1A1ED200E7D95C /* LNForwardingDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNForwardingDelegate.h; sourceTree = ""; };
397AFBFA1F1A1ED200E7D95C /* LNForwardingDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNForwardingDelegate.m; sourceTree = ""; };
397AFBFF1F1A21DD00E7D95C /* LNPopupLongPressGestureRecognizer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LNPopupLongPressGestureRecognizer.h; sourceTree = ""; };
@@ -125,21 +147,43 @@
397D9A152687474D005164AB /* _LNPopupBarBackgroundView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupBarBackgroundView.h; sourceTree = ""; };
397D9A162687474D005164AB /* _LNPopupBarBackgroundView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _LNPopupBarBackgroundView.m; sourceTree = ""; };
399BA1D22A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupUIBarAppearanceProxy.h; sourceTree = ""; };
- 399BA1D32A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _LNPopupUIBarAppearanceProxy.m; sourceTree = ""; };
- 399D37B62ADC672D00EA5038 /* _LNPopupBarShadowedImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupBarShadowedImageView.h; sourceTree = ""; };
- 399D37B72ADC672D00EA5038 /* _LNPopupBarShadowedImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _LNPopupBarShadowedImageView.m; sourceTree = ""; };
+ 399BA1D32A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _LNPopupUIBarAppearanceProxy.mm; sourceTree = ""; };
+ 399D37B62ADC672D00EA5038 /* LNPopupImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LNPopupImageView.h; sourceTree = ""; };
+ 399D37B72ADC672D00EA5038 /* LNPopupImageView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LNPopupImageView.mm; sourceTree = ""; };
39A0DDB126F7894700E5D751 /* NSAttributedString+LNPopupSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+LNPopupSupport.h"; sourceTree = ""; };
39A0DDB226F7894700E5D751 /* NSAttributedString+LNPopupSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+LNPopupSupport.m"; sourceTree = ""; };
39A924991B5852ED003C1C19 /* LNPopupController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LNPopupController.h; sourceTree = ""; };
39AC478F26C69AC9001D53C2 /* LNMath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNMath.h; sourceTree = ""; };
39AC479026C69AC9001D53C2 /* LNMath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNMath.m; sourceTree = ""; };
+ 39AF84EC2DA12D2D00E59C44 /* _LNPopupBarAppearanceLegacySupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupBarAppearanceLegacySupport.h; sourceTree = ""; };
+ 39AF84ED2DA12D2D00E59C44 /* _LNPopupBarAppearanceLegacySupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _LNPopupBarAppearanceLegacySupport.m; sourceTree = ""; };
39B3EB0B24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+LNPopupSupportPrivate.h"; sourceTree = ""; };
- 39B3EB0C24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+LNPopupSupportPrivate.m"; sourceTree = ""; };
+ 39B3EB0C24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIView+LNPopupSupportPrivate.mm"; sourceTree = ""; };
+ 39B54FFF2D92D29000B474EC /* _LNPopupTransitionAnimator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupTransitionAnimator.h; sourceTree = ""; };
+ 39B550002D92D29000B474EC /* _LNPopupTransitionAnimator.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _LNPopupTransitionAnimator.mm; sourceTree = ""; };
+ 39B550012D92D29000B474EC /* _LNPopupTransitionCloseAnimator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupTransitionCloseAnimator.h; sourceTree = ""; };
+ 39B550022D92D29000B474EC /* _LNPopupTransitionCloseAnimator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _LNPopupTransitionCloseAnimator.m; sourceTree = ""; };
+ 39B550032D92D29000B474EC /* _LNPopupTransitionGenericCloseAnimator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupTransitionGenericCloseAnimator.h; sourceTree = ""; };
+ 39B550042D92D29000B474EC /* _LNPopupTransitionGenericCloseAnimator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _LNPopupTransitionGenericCloseAnimator.m; sourceTree = ""; };
+ 39B550052D92D29000B474EC /* _LNPopupTransitionGenericOpenAnimator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupTransitionGenericOpenAnimator.h; sourceTree = ""; };
+ 39B550062D92D29000B474EC /* _LNPopupTransitionGenericOpenAnimator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _LNPopupTransitionGenericOpenAnimator.m; sourceTree = ""; };
+ 39B550072D92D29000B474EC /* _LNPopupTransitionOpenAnimator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupTransitionOpenAnimator.h; sourceTree = ""; };
+ 39B550082D92D29000B474EC /* _LNPopupTransitionOpenAnimator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _LNPopupTransitionOpenAnimator.m; sourceTree = ""; };
+ 39B550092D92D29000B474EC /* _LNPopupTransitionPreferredCloseAnimator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupTransitionPreferredCloseAnimator.h; sourceTree = ""; };
+ 39B5500A2D92D29000B474EC /* _LNPopupTransitionPreferredCloseAnimator.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _LNPopupTransitionPreferredCloseAnimator.mm; sourceTree = ""; };
+ 39B5500B2D92D29000B474EC /* _LNPopupTransitionPreferredOpenAnimator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupTransitionPreferredOpenAnimator.h; sourceTree = ""; };
+ 39B5500C2D92D29000B474EC /* _LNPopupTransitionPreferredOpenAnimator.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _LNPopupTransitionPreferredOpenAnimator.mm; sourceTree = ""; };
+ 39C553DD2D8F2B3200F8D8B2 /* _LNPopupTransitionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupTransitionView.h; sourceTree = ""; };
+ 39C553DE2D8F2B3200F8D8B2 /* _LNPopupTransitionView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _LNPopupTransitionView.mm; sourceTree = ""; };
+ 39C553E22D91924900F8D8B2 /* LNPopupImageView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LNPopupImageView+Private.h"; sourceTree = ""; };
39DB52E51B5823330061C589 /* LNPopupController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LNPopupController.framework; sourceTree = BUILT_PRODUCTS_DIR; };
39E7A602200B5157007AF3AD /* _LNPopupSwizzlingUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _LNPopupSwizzlingUtils.h; sourceTree = ""; };
39E7A603200B5157007AF3AD /* _LNPopupSwizzlingUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _LNPopupSwizzlingUtils.m; sourceTree = ""; };
39E9FE10275B97CC00A47D61 /* LNPopupDefinitions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LNPopupDefinitions.h; sourceTree = ""; };
39F8228726B80B6F0070DDBA /* SwiftRefinements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SwiftRefinements.swift; path = ../../LNPCSwiftRefinements/SwiftRefinements.swift; sourceTree = ""; };
+ 39F9DEC92C84A207005CA5FB /* _LNPopupBase64Utils.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = _LNPopupBase64Utils.hh; sourceTree = ""; };
+ 39FA8FCF2D9F84000068FD0D /* LNPopupDebug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LNPopupDebug.h; sourceTree = ""; };
+ 39FA8FD02D9F84000068FD0D /* LNPopupDebug.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LNPopupDebug.m; sourceTree = ""; };
46ECC2AE1BF31025005CE96C /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */
@@ -157,6 +201,7 @@
3947E1AB1B61CDD70001178B /* Implementation */ = {
isa = PBXGroup;
children = (
+ 39F9DEC92C84A207005CA5FB /* _LNPopupBase64Utils.hh */,
39E7A602200B5157007AF3AD /* _LNPopupSwizzlingUtils.h */,
39E7A603200B5157007AF3AD /* _LNPopupSwizzlingUtils.m */,
394A85BF1B630992004FFC61 /* _LNWeakRef.h */,
@@ -170,22 +215,25 @@
397D9A162687474D005164AB /* _LNPopupBarBackgroundView.m */,
394005FA2AC3C60900291421 /* _LNPopupBarBackgroundMaskView.h */,
394005FB2AC3C60900291421 /* _LNPopupBarBackgroundMaskView.m */,
- 399D37B62ADC672D00EA5038 /* _LNPopupBarShadowedImageView.h */,
- 399D37B72ADC672D00EA5038 /* _LNPopupBarShadowedImageView.m */,
+ 39C553E22D91924900F8D8B2 /* LNPopupImageView+Private.h */,
+ 399D37B72ADC672D00EA5038 /* LNPopupImageView.mm */,
+ 39C553DD2D8F2B3200F8D8B2 /* _LNPopupTransitionView.h */,
+ 39C553DE2D8F2B3200F8D8B2 /* _LNPopupTransitionView.mm */,
3947E1B81B6300370001178B /* LNPopupBar+Private.h */,
- 3947E1A41B61CD650001178B /* LNPopupBar.m */,
+ 3947E1A41B61CD650001178B /* LNPopupBar.mm */,
393E4EAC26713E5700929E47 /* LNPopupBarAppearance+Private.h */,
- 393E4EA52670F12500929E47 /* LNPopupBarAppearance.m */,
+ 393E4EA52670F12500929E47 /* LNPopupBarAppearance.mm */,
396A8DE626BEA36B005914B0 /* LNPopupBarAppearanceChainProxy.h */,
396A8DE726BEA36B005914B0 /* LNPopupBarAppearanceChainProxy.m */,
- 39500DFD2B4DD02300E20D08 /* _LNPopupBarAppearanceLegacySupport.h */,
- 39500DFE2B4DD02300E20D08 /* _LNPopupBarAppearanceLegacySupport.m */,
39109DA11DD8A305004B5FAB /* LNPopupCloseButton+Private.h */,
- 3947E1A81B61CDA40001178B /* LNPopupCloseButton.m */,
+ 3947E1A81B61CDA40001178B /* LNPopupCloseButton.mm */,
3907CDB924D9BD8A007C9300 /* LNPopupContentView+Private.h */,
3907CDB624D9BD6E007C9300 /* LNPopupContentView.m */,
+ 39B54FFE2D92D26300B474EC /* TransitionAnimators */,
3947E1B01B61CE4A0001178B /* LNPopupController.h */,
- 3947E1B11B61CE4A0001178B /* LNPopupController.m */,
+ 3947E1B11B61CE4A0001178B /* LNPopupController.mm */,
+ 39FA8FCF2D9F84000068FD0D /* LNPopupDebug.h */,
+ 39FA8FD02D9F84000068FD0D /* LNPopupDebug.m */,
39599BAC1E02CDF4008EE386 /* LNPopupCustomBarViewController+Private.h */,
39599BA91E02CD65008EE386 /* LNPopupCustomBarViewController.m */,
394A85BB1B6306AE004FFC61 /* LNPopupItem+Private.h */,
@@ -193,18 +241,20 @@
39314A501B6AE7A400574D3C /* MarqueeLabel.h */,
39314A511B6AE7A400574D3C /* MarqueeLabel.m */,
396D62702610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.h */,
- 396D62712610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.m */,
+ 396D62712610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.mm */,
399BA1D22A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.h */,
- 399BA1D32A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.m */,
+ 399BA1D32A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.mm */,
39B3EB0B24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.h */,
- 39B3EB0C24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.m */,
- 3947E1A01B61CD1F0001178B /* UIViewController+LNPopupSupport.m */,
+ 39B3EB0C24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.mm */,
+ 3947E1A01B61CD1F0001178B /* UIViewController+LNPopupSupport.mm */,
394A85C71B63FB96004FFC61 /* UIViewController+LNPopupSupportPrivate.h */,
- 394A85C81B63FE52004FFC61 /* UIViewController+LNPopupSupportPrivate.m */,
+ 394A85C81B63FE52004FFC61 /* UIViewController+LNPopupSupportPrivate.mm */,
39AC478F26C69AC9001D53C2 /* LNMath.h */,
39AC479026C69AC9001D53C2 /* LNMath.m */,
39A0DDB126F7894700E5D751 /* NSAttributedString+LNPopupSupport.h */,
39A0DDB226F7894700E5D751 /* NSAttributedString+LNPopupSupport.m */,
+ 39AF84EC2DA12D2D00E59C44 /* _LNPopupBarAppearanceLegacySupport.h */,
+ 39AF84ED2DA12D2D00E59C44 /* _LNPopupBarAppearanceLegacySupport.m */,
);
name = Implementation;
path = Private;
@@ -213,6 +263,8 @@
397AFBFE1F1A216400E7D95C /* GestureRecognizers */ = {
isa = PBXGroup;
children = (
+ 3922006F2C663A03008AFD36 /* _LNPopupAddressInfo.h */,
+ 392200712C663A23008AFD36 /* _LNPopupAddressInfo.mm */,
397AFBF91F1A1ED200E7D95C /* LNForwardingDelegate.h */,
397AFBFA1F1A1ED200E7D95C /* LNForwardingDelegate.m */,
39222AD91F1A1C5800388E06 /* LNPopupOpenTapGestureRecognizer.h */,
@@ -225,6 +277,27 @@
name = GestureRecognizers;
sourceTree = "";
};
+ 39B54FFE2D92D26300B474EC /* TransitionAnimators */ = {
+ isa = PBXGroup;
+ children = (
+ 39B54FFF2D92D29000B474EC /* _LNPopupTransitionAnimator.h */,
+ 39B550002D92D29000B474EC /* _LNPopupTransitionAnimator.mm */,
+ 39B550012D92D29000B474EC /* _LNPopupTransitionCloseAnimator.h */,
+ 39B550022D92D29000B474EC /* _LNPopupTransitionCloseAnimator.m */,
+ 39B550032D92D29000B474EC /* _LNPopupTransitionGenericCloseAnimator.h */,
+ 39B550042D92D29000B474EC /* _LNPopupTransitionGenericCloseAnimator.m */,
+ 39B550092D92D29000B474EC /* _LNPopupTransitionPreferredCloseAnimator.h */,
+ 39B5500A2D92D29000B474EC /* _LNPopupTransitionPreferredCloseAnimator.mm */,
+ 39B550072D92D29000B474EC /* _LNPopupTransitionOpenAnimator.h */,
+ 39B550082D92D29000B474EC /* _LNPopupTransitionOpenAnimator.m */,
+ 39B550052D92D29000B474EC /* _LNPopupTransitionGenericOpenAnimator.h */,
+ 39B550062D92D29000B474EC /* _LNPopupTransitionGenericOpenAnimator.m */,
+ 39B5500B2D92D29000B474EC /* _LNPopupTransitionPreferredOpenAnimator.h */,
+ 39B5500C2D92D29000B474EC /* _LNPopupTransitionPreferredOpenAnimator.mm */,
+ );
+ name = TransitionAnimators;
+ sourceTree = "";
+ };
39DB52DB1B5823330061C589 = {
isa = PBXGroup;
children = (
@@ -253,6 +326,7 @@
390DD0101BB2EAC30064DB4A /* LNPopupContentView.h */,
39599BA81E02CD65008EE386 /* LNPopupCustomBarViewController.h */,
394A85B71B6304F5004FFC61 /* LNPopupItem.h */,
+ 399D37B62ADC672D00EA5038 /* LNPopupImageView.h */,
3947E19F1B61CD1F0001178B /* UIViewController+LNPopupSupport.h */,
39F8228726B80B6F0070DDBA /* SwiftRefinements.swift */,
46ECC2AE1BF31025005CE96C /* Info.plist */,
@@ -272,23 +346,33 @@
390DD0111BB2EAC30064DB4A /* LNPopupContentView.h in Headers */,
394A85B61B630409004FFC61 /* LNPopupBar.h in Headers */,
398C2903260CCDA6000690FB /* LNPopupContentView+Private.h in Headers */,
+ 39F9DECA2C84A211005CA5FB /* _LNPopupBase64Utils.hh in Headers */,
39599BAA1E02CD65008EE386 /* LNPopupCustomBarViewController.h in Headers */,
+ 39FA8FD12D9F84000068FD0D /* LNPopupDebug.h in Headers */,
399BA1D42A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.h in Headers */,
39C81A321B642DDD00D3B645 /* LNPopupItem.h in Headers */,
39A0DDB326F7894700E5D751 /* NSAttributedString+LNPopupSupport.h in Headers */,
3947E1A11B61CD1F0001178B /* UIViewController+LNPopupSupport.h in Headers */,
393E4EAA2670F2D000929E47 /* LNPopupBarAppearance.h in Headers */,
- 39500DFF2B4DD02300E20D08 /* _LNPopupBarAppearanceLegacySupport.h in Headers */,
3947E1B21B61CE4A0001178B /* LNPopupController.h in Headers */,
39E9FE11275B984B00A47D61 /* LNPopupDefinitions.h in Headers */,
39B3EB0D24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.h in Headers */,
3947E1BA1B6300370001178B /* LNPopupBar+Private.h in Headers */,
398C2901260CCDA0000690FB /* UIViewController+LNPopupSupportPrivate.h in Headers */,
+ 39C553E02D8F2B3200F8D8B2 /* _LNPopupTransitionView.h in Headers */,
394005FC2AC3C60900291421 /* _LNPopupBarBackgroundMaskView.h in Headers */,
+ 39AF84EF2DA12D2D00E59C44 /* _LNPopupBarAppearanceLegacySupport.h in Headers */,
397D9A172687474E005164AB /* _LNPopupBarBackgroundView.h in Headers */,
- 399D37B82ADC672D00EA5038 /* _LNPopupBarShadowedImageView.h in Headers */,
+ 399D37B82ADC672D00EA5038 /* LNPopupImageView.h in Headers */,
397AFC051F1A229400E7D95C /* LNPopupInteractionPanGestureRecognizer.h in Headers */,
396A8DE826BEA36B005914B0 /* LNPopupBarAppearanceChainProxy.h in Headers */,
+ 39B550142D92D29000B474EC /* _LNPopupTransitionPreferredOpenAnimator.h in Headers */,
+ 39B550152D92D29000B474EC /* _LNPopupTransitionOpenAnimator.h in Headers */,
+ 39B550162D92D29000B474EC /* _LNPopupTransitionCloseAnimator.h in Headers */,
+ 39B550172D92D29000B474EC /* _LNPopupTransitionAnimator.h in Headers */,
+ 39B550182D92D29000B474EC /* _LNPopupTransitionGenericCloseAnimator.h in Headers */,
+ 39B550192D92D29000B474EC /* _LNPopupTransitionPreferredCloseAnimator.h in Headers */,
+ 39B5501A2D92D29000B474EC /* _LNPopupTransitionGenericOpenAnimator.h in Headers */,
39E7A604200B5157007AF3AD /* _LNPopupSwizzlingUtils.h in Headers */,
396D62722610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.h in Headers */,
39314A521B6AE7A400574D3C /* MarqueeLabel.h in Headers */,
@@ -296,6 +380,7 @@
39599BAD1E02CDF4008EE386 /* LNPopupCustomBarViewController+Private.h in Headers */,
397AFBFB1F1A1ED200E7D95C /* LNForwardingDelegate.h in Headers */,
39222ADB1F1A1C5800388E06 /* LNPopupOpenTapGestureRecognizer.h in Headers */,
+ 39C553E32D91925300F8D8B2 /* LNPopupImageView+Private.h in Headers */,
397AFC011F1A21DD00E7D95C /* LNPopupLongPressGestureRecognizer.h in Headers */,
390031692AC06FE10046D3DD /* _LNPopupBackgroundShadowView.h in Headers */,
394A85C11B630992004FFC61 /* _LNWeakRef.h in Headers */,
@@ -335,7 +420,7 @@
isa = PBXProject;
attributes = {
LastUpgradeCheck = 9999;
- ORGANIZATIONNAME = "Leo Natan";
+ ORGANIZATIONNAME = "Léo Natan";
TargetAttributes = {
39DB52E41B5823330061C589 = {
CreatedOnToolsVersion = 7.0;
@@ -344,7 +429,7 @@
};
};
buildConfigurationList = 39DB52DF1B5823330061C589 /* Build configuration list for PBXProject "LNPopupController" */;
- compatibilityVersion = "Xcode 3.2";
+ compatibilityVersion = "Xcode 15.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -354,7 +439,6 @@
mainGroup = 39DB52DB1B5823330061C589;
packageReferences = (
);
- productRefGroup = 39DB52E61B5823330061C589 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
@@ -379,32 +463,43 @@
buildActionMask = 2147483647;
files = (
39F8228826B80B6F0070DDBA /* SwiftRefinements.swift in Sources */,
- 399D37B92ADC672D00EA5038 /* _LNPopupBarShadowedImageView.m in Sources */,
+ 399D37B92ADC672D00EA5038 /* LNPopupImageView.mm in Sources */,
39E7A605200B5157007AF3AD /* _LNPopupSwizzlingUtils.m in Sources */,
3900316A2AC06FE10046D3DD /* _LNPopupBackgroundShadowView.m in Sources */,
397AFC021F1A21DD00E7D95C /* LNPopupLongPressGestureRecognizer.m in Sources */,
39222ADC1F1A1C5800388E06 /* LNPopupOpenTapGestureRecognizer.m in Sources */,
- 39500E002B4DD02300E20D08 /* _LNPopupBarAppearanceLegacySupport.m in Sources */,
- 3947E1AA1B61CDA40001178B /* LNPopupCloseButton.m in Sources */,
+ 3975A5C42C663B520027FCDD /* _LNPopupAddressInfo.mm in Sources */,
+ 3947E1AA1B61CDA40001178B /* LNPopupCloseButton.mm in Sources */,
396A8DE926BEA36B005914B0 /* LNPopupBarAppearanceChainProxy.m in Sources */,
- 39B3EB0E24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.m in Sources */,
+ 39C553DF2D8F2B3200F8D8B2 /* _LNPopupTransitionView.mm in Sources */,
+ 39B3EB0E24D4ECC40034B81D /* UIView+LNPopupSupportPrivate.mm in Sources */,
+ 39AF84EE2DA12D2D00E59C44 /* _LNPopupBarAppearanceLegacySupport.m in Sources */,
394A85C21B630992004FFC61 /* _LNWeakRef.m in Sources */,
394A85BA1B6304F5004FFC61 /* LNPopupItem.m in Sources */,
- 3947E1B31B61CE4A0001178B /* LNPopupController.m in Sources */,
+ 3947E1B31B61CE4A0001178B /* LNPopupController.mm in Sources */,
397AFBFC1F1A1ED200E7D95C /* LNForwardingDelegate.m in Sources */,
39314A531B6AE7A400574D3C /* MarqueeLabel.m in Sources */,
3907CDB824D9BD6E007C9300 /* LNPopupContentView.m in Sources */,
391481BA1DCFA514002416D1 /* LNChevronView.m in Sources */,
394005FD2AC3C60900291421 /* _LNPopupBarBackgroundMaskView.m in Sources */,
- 399BA1D52A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.m in Sources */,
+ 399BA1D52A9E975E00CA5167 /* _LNPopupUIBarAppearanceProxy.mm in Sources */,
397D9A182687474E005164AB /* _LNPopupBarBackgroundView.m in Sources */,
- 3947E1A61B61CD650001178B /* LNPopupBar.m in Sources */,
+ 3947E1A61B61CD650001178B /* LNPopupBar.mm in Sources */,
+ 392200702C663A03008AFD36 /* _LNPopupAddressInfo.h in Sources */,
39A0DDB426F7894700E5D751 /* NSAttributedString+LNPopupSupport.m in Sources */,
- 394A85C91B63FE52004FFC61 /* UIViewController+LNPopupSupportPrivate.m in Sources */,
+ 394A85C91B63FE52004FFC61 /* UIViewController+LNPopupSupportPrivate.mm in Sources */,
397AFC061F1A229400E7D95C /* LNPopupInteractionPanGestureRecognizer.m in Sources */,
- 393E4EA72670F12500929E47 /* LNPopupBarAppearance.m in Sources */,
- 396D62732610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.m in Sources */,
- 3947E1A21B61CD1F0001178B /* UIViewController+LNPopupSupport.m in Sources */,
+ 393E4EA72670F12500929E47 /* LNPopupBarAppearance.mm in Sources */,
+ 39B5500D2D92D29000B474EC /* _LNPopupTransitionAnimator.mm in Sources */,
+ 39B5500E2D92D29000B474EC /* _LNPopupTransitionCloseAnimator.m in Sources */,
+ 39B5500F2D92D29000B474EC /* _LNPopupTransitionOpenAnimator.m in Sources */,
+ 39B550102D92D29000B474EC /* _LNPopupTransitionPreferredOpenAnimator.mm in Sources */,
+ 39B550112D92D29000B474EC /* _LNPopupTransitionPreferredCloseAnimator.mm in Sources */,
+ 39B550122D92D29000B474EC /* _LNPopupTransitionGenericOpenAnimator.m in Sources */,
+ 39B550132D92D29000B474EC /* _LNPopupTransitionGenericCloseAnimator.m in Sources */,
+ 396D62732610A42000D03A42 /* UIContextMenuInteraction+LNPopupSupportPrivate.mm in Sources */,
+ 39FA8FD22D9F84000068FD0D /* LNPopupDebug.m in Sources */,
+ 3947E1A21B61CD1F0001178B /* UIViewController+LNPopupSupport.mm in Sources */,
39599BAB1E02CD65008EE386 /* LNPopupCustomBarViewController.m in Sources */,
39AC479226C69ACA001D53C2 /* LNMath.m in Sources */,
);
@@ -460,8 +555,6 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MACH_O_TYPE = mh_dylib;
MTL_ENABLE_DEBUG_INFO = YES;
@@ -515,8 +608,6 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MACH_O_TYPE = mh_dylib;
MTL_ENABLE_DEBUG_INFO = NO;
@@ -539,8 +630,6 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
- DOCC_EXTRACT_OBJC_INFO_FOR_SWIFT_SYMBOLS = YES;
- DOCC_EXTRACT_SWIFT_INFO_FOR_OBJC_SYMBOLS = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -549,10 +638,14 @@
INFOPLIST_FILE = "$(SRCROOT)/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 12.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = com.LeoNatan.LNPopupController;
PRODUCT_NAME = LNPopupController;
- RUN_DOCUMENTATION_COMPILER = YES;
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -574,8 +667,6 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
- DOCC_EXTRACT_OBJC_INFO_FOR_SWIFT_SYMBOLS = YES;
- DOCC_EXTRACT_SWIFT_INFO_FOR_OBJC_SYMBOLS = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -584,10 +675,14 @@
INFOPLIST_FILE = "$(SRCROOT)/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 12.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = com.LeoNatan.LNPopupController;
PRODUCT_NAME = LNPopupController;
- RUN_DOCUMENTATION_COMPILER = YES;
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_COMPILATION_MODE = wholemodule;
diff --git a/LNPopupController/LNPopupController/LNPopupBar.h b/LNPopupController/LNPopupController/LNPopupBar.h
index af6b5db..6227b14 100644
--- a/LNPopupController/LNPopupController/LNPopupBar.h
+++ b/LNPopupController/LNPopupController/LNPopupBar.h
@@ -2,8 +2,8 @@
// LNPopupBar.h
// LNPopupController
//
-// Created by Leo Natan on 7/24/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
@@ -11,6 +11,7 @@
#import
#import
#import
+#import
#define LN_UNAVAILABLE_PREVIEWING_MSG "Add context menu interaction or register for previewing directly on the popup bar view."
@@ -73,7 +74,7 @@ NS_SWIFT_UI_ACTOR
@property (nullable, nonatomic, copy, readonly) NSArray* trailingBarButtonItems;
/// An image view displayed when the bar style is prominent. (read-only)
-@property (nonatomic, strong, readonly) UIImageView* imageView;
+@property (nonatomic, strong, readonly) LNPopupImageView* imageView;
/// The popup bar style.
@property (nonatomic, assign) LNPopupBarStyle barStyle UI_APPEARANCE_SELECTOR;
@@ -83,8 +84,13 @@ NS_SWIFT_UI_ACTOR
/// Use this property's value to determine, at runtime, what the result of `LNPopupBarStyleDefault` is.
@property (nonatomic, assign, readonly) LNPopupBarStyle effectiveBarStyle;
+/// In wide enough environments, such as iPadOS, limit the width of content of floating bars to a system-determined value.
+///
+/// Defaults to `true`.
+@property (nonatomic, assign) BOOL limitFloatingContentWidth;
+
/// Describes the appearance attributes for the popup bar to use.
-@property (nonatomic, copy, null_resettable) LNPopupBarAppearance* standardAppearance UI_APPEARANCE_SELECTOR API_AVAILABLE(ios(13.0));
+@property (nonatomic, copy, null_resettable) LNPopupBarAppearance* standardAppearance UI_APPEARANCE_SELECTOR API_AVAILABLE(ios(13.0));
/// The popup bar's progress view style.
@property (nonatomic, assign) LNPopupBarProgressViewStyle progressViewStyle UI_APPEARANCE_SELECTOR;
@@ -110,66 +116,4 @@ NS_SWIFT_UI_ACTOR
@end
-#pragma mark Deprecations
-
-extern const UIBlurEffectStyle LNBackgroundStyleInherit LN_UNAVAILABLE_API("Use LNPopupBarAppearance instead.");
-
-@interface LNPopupBar (Deprecated)
-
-/// If `true`, the popup bar will automatically inherit its style from the bottom docking view.
-@property (nonatomic, assign) BOOL inheritsVisualStyleFromDockingView LN_UNAVAILABLE_API("Use inheritsAppearanceFromDockingView instead.");
-
-/// The popup bar background style that specifies its appearance.
-///
-/// Use `LNBackgroundStyleInherit` value to inherit the docking view's bar style if possible, or use a system default.
-///
-/// Defaults to `LNBackgroundStyleInherit`.
-@property (nonatomic, assign) UIBlurEffectStyle backgroundStyle LN_UNAVAILABLE_API("Use LNPopupBarAppearance.backgroundEffect instead.");
-
-/// The tint color to apply to the popup bar background.
-@property (nullable, nonatomic, strong) UIColor* barTintColor LN_UNAVAILABLE_API("Use LNPopupBarAppearance.backgroundColor instead.");
-
-/// A Boolean value that indicates whether the popup bar is translucent (`true`) or not (`false`).
-@property(nonatomic, assign, getter=isTranslucent) BOOL translucent LN_UNAVAILABLE_API("Use LNPopupBarAppearance.configureWithOpaqueBackground() instead.");
-
-/// Display attributes for the popup bar’s title text.
-///
-/// You may specify the font, text color, and shadow properties for the title in the text attributes dictionary, using the keys found in `NSAttributedString.h`.
-@property (nullable, nonatomic, copy) NSDictionary* titleTextAttributes LN_UNAVAILABLE_API("Use LNPopupBarAppearance.titleTextAttributes instead.");
-
-/// Display attributes for the popup bar’s subtitle text.
-///
-/// You may specify the font, text color, and shadow properties for the title in the text attributes dictionary, using the keys found in `NSAttributedString.h`.
-@property (nullable, nonatomic, copy) NSDictionary* subtitleTextAttributes LN_UNAVAILABLE_API("Use LNPopupBarAppearance.subtitleTextAttributes instead.");
-
-/// When enabled, titles and subtitles that are longer than the space available will scroll text over time.
-///
-/// Defaults to `false`.
-@property (nonatomic, assign) BOOL marqueeScrollEnabled LN_UNAVAILABLE_API("Use LNPopupBarAppearance.marqueeScrollEnabled instead.");
-
-/// The scroll rate, in points, of the title and subtitle marquee animation.
-///
-/// Defaults to `30`.
-@property (nonatomic, assign) CGFloat marqueeScrollRate LN_UNAVAILABLE_API("Use LNPopupBarAppearance.marqueeScrollRate instead.");
-
-/// The delay, in seconds, before starting the title and subtitle marquee animation.
-///
-/// Defaults to `2`.
-@property (nonatomic, assign) NSTimeInterval marqueeScrollDelay LN_UNAVAILABLE_API("Use LNPopupBarAppearance.marqueeScrollDelay instead.");
-
-/// When enabled, the title and subtitle marquee scroll animations will be coordinated.
-///
-/// If either the title or subtitle of the current popup item change, the animation will reset so the two can scroll together.
-///
-/// Defaults to `true`.
-@property (nonatomic, assign) BOOL coordinateMarqueeScroll LN_UNAVAILABLE_API("Use LNPopupBarAppearance.coordinateMarqueeScroll instead.");
-
-/// An array of custom bar button items to display on the left side. (read-only)
-@property (nullable, nonatomic, copy, readonly) NSArray* leftBarButtonItems LN_UNAVAILABLE_API("Use leadingBarButtonItems instead.");
-
-/// An array of custom bar button items to display on the right side. (read-only)
-@property (nullable, nonatomic, copy, readonly) NSArray* rightBarButtonItems LN_UNAVAILABLE_API("Use barButtonItems or trailingBarButtonItems instead.");
-
-@end
-
NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/LNPopupBarAppearance.h b/LNPopupController/LNPopupController/LNPopupBarAppearance.h
index 07423c6..d630293 100644
--- a/LNPopupController/LNPopupController/LNPopupBarAppearance.h
+++ b/LNPopupController/LNPopupController/LNPopupBarAppearance.h
@@ -2,8 +2,8 @@
// LNPopupBarAppearance.h
// LNPopupController
//
-// Created by Leo Natan on 6/9/21.
-// Copyright © 2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-06-20.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/LNPopupCloseButton.h b/LNPopupController/LNPopupController/LNPopupCloseButton.h
index a288bb3..1ca3d27 100644
--- a/LNPopupController/LNPopupController/LNPopupCloseButton.h
+++ b/LNPopupController/LNPopupController/LNPopupCloseButton.h
@@ -2,8 +2,8 @@
// LNPopupCloseButton.h
// LNPopupController
//
-// Created by Leo Natan on 7/24/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
@@ -36,11 +36,16 @@ NS_SWIFT_UI_ACTOR
@interface LNPopupCloseButton : UIButton
/// Gets or sets the style of the popup close button. Has the same effect as setting the `LNPopupContentView.popupCloseButtonStyle` property of the popup content view.
-@property (nonatomic) LNPopupCloseButtonStyle style UI_APPEARANCE_SELECTOR;
+@property (nonatomic, assign) LNPopupCloseButtonStyle style UI_APPEARANCE_SELECTOR;
+
+/// The effective popup close button style used by the system. (read-only)
+///
+/// Use this property's value to determine, at runtime, what the result of `LNPopupCloseButtonStyleDefault` is.
+@property (nonatomic, assign, readonly) LNPopupCloseButtonStyle effectiveStyle;
/// The button’s background view. (read-only)
///
-/// The value of this property will be `nil` if ``style`` is not set to `LNPopupCloseButtonStyleRound`.
+/// The value of this property will be `nil` if `style` is set to any value other than `LNPopupCloseButtonStyleRound`.
///
/// @note Although this property is read-only, its own properties are read/write. Use these properties to configure the appearance and behavior of the button’s background view.
@property (nonatomic, strong, readonly) UIVisualEffectView* backgroundView;
diff --git a/LNPopupController/LNPopupController/LNPopupContentView.h b/LNPopupController/LNPopupController/LNPopupContentView.h
index 2b50f3a..073f394 100644
--- a/LNPopupController/LNPopupController/LNPopupContentView.h
+++ b/LNPopupController/LNPopupController/LNPopupContentView.h
@@ -2,8 +2,8 @@
// LNPopupItem.h
// LNPopupController
//
-// Created by Leo Natan on 7/25/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-09-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
@@ -25,7 +25,12 @@ NS_SWIFT_UI_ACTOR
/// The popup close button style.
///
/// Defaults to `LNPopupCloseButtonStyleDefault`.
-@property (nonatomic) LNPopupCloseButtonStyle popupCloseButtonStyle UI_APPEARANCE_SELECTOR;
+@property (nonatomic, assign) LNPopupCloseButtonStyle popupCloseButtonStyle UI_APPEARANCE_SELECTOR;
+
+/// The effective popup close button style used by the system. (read-only)
+///
+/// Use this property's value to determine, at runtime, what the result of `LNPopupCloseButtonStyleDefault` is.
+@property (nonatomic, assign, readonly) LNPopupCloseButtonStyle effectivePopupCloseButtonStyle;
/// The popup close button. (read-only)
@property (nonatomic, strong, readonly) LNPopupCloseButton* popupCloseButton;
@@ -44,24 +49,4 @@ NS_SWIFT_UI_ACTOR
@end
-#pragma mark Deprecations
-
-extern const UIBlurEffectStyle LNBackgroundStyleInherit LN_UNAVAILABLE_API("Use backgroundEffect instead.");
-
-@interface LNPopupContentView (Deprecated)
-
-/// Attempt to automatically move the popup close button under top bars, such as navigation bars.
-///
-/// Note: No longer supported. Instead, implement `UIViewController.positionPopupCloseButton()` and position the button in your content controller's view hierarchy.
-@property (nonatomic) BOOL popupCloseButtonAutomaticallyUnobstructsTopBars LN_UNAVAILABLE_API("No longer supported. Instead, implement UIViewController.positionPopupCloseButton() and position the button in your content controller's view hierarchy.");
-
-/// The popup content view background style, used when the popup content controller's view has transparency.
-///
-/// Use `LNBackgroundStyleInherit` value to inherit the popup bar's background style if possible.
-///
-/// Defaults to `LNBackgroundStyleInherit`.
-@property (nonatomic, assign) UIBlurEffectStyle backgroundStyle LN_UNAVAILABLE_API("Use backgroundEffect instead.");
-
-@end
-
NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/LNPopupCustomBarViewController.h b/LNPopupController/LNPopupController/LNPopupCustomBarViewController.h
index 208cd30..7da1e52 100644
--- a/LNPopupController/LNPopupController/LNPopupCustomBarViewController.h
+++ b/LNPopupController/LNPopupController/LNPopupCustomBarViewController.h
@@ -2,8 +2,8 @@
// LNPopupBarContentViewController.h
// LNPopupController
//
-// Created by Leo Natan on 15/12/2016.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2016-12-30.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/LNPopupDefinitions.h b/LNPopupController/LNPopupController/LNPopupDefinitions.h
index 2c7c65c..cf3cee5 100644
--- a/LNPopupController/LNPopupController/LNPopupDefinitions.h
+++ b/LNPopupController/LNPopupController/LNPopupDefinitions.h
@@ -2,8 +2,8 @@
// LNPopupDefinitions.h
// LNPopupController
//
-// Created by Leo Natan on 12/4/21.
-// Copyright © 2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-12-16.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#ifndef LNPopupDefinitions_h
diff --git a/LNPopupController/LNPopupController/LNPopupImageView.h b/LNPopupController/LNPopupController/LNPopupImageView.h
new file mode 100644
index 0000000..a4a4026
--- /dev/null
+++ b/LNPopupController/LNPopupController/LNPopupImageView.h
@@ -0,0 +1,27 @@
+//
+// LNPopupImageView.h
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// A specialized `UIImageView` subclass, allowing setting a shadow and corner radius.
+///
+/// When used inside a popup content view, instances of this class are especially suited as image transition targets.
+///
+/// See `UIViewController.viewForPopupTransition(from:to:)`.
+@interface LNPopupImageView : UIImageView
+
+/// The corner radius of the image view.
+@property (nonatomic, assign) CGFloat cornerRadius;
+/// The shadow displayed underneath the image view.
+@property (nonatomic, copy) NSShadow* shadow;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/LNPopupItem.h b/LNPopupController/LNPopupController/LNPopupItem.h
index ceb390e..82cca51 100644
--- a/LNPopupController/LNPopupController/LNPopupItem.h
+++ b/LNPopupController/LNPopupController/LNPopupItem.h
@@ -2,8 +2,8 @@
// LNPopupItem.h
// LNPopupController
//
-// Created by Leo Natan on 7/25/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/LNChevronView.h b/LNPopupController/LNPopupController/Private/LNChevronView.h
index 5095749..4959b6a 100644
--- a/LNPopupController/LNPopupController/Private/LNChevronView.h
+++ b/LNPopupController/LNPopupController/Private/LNChevronView.h
@@ -1,8 +1,8 @@
//
// LNChevronView.h
//
-// Created by Leo Natan on 16/9/16.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2016-12-02.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#define LNChevronView __LNChevronView
diff --git a/LNPopupController/LNPopupController/Private/LNChevronView.m b/LNPopupController/LNPopupController/Private/LNChevronView.m
index 0eafba4..474e95a 100644
--- a/LNPopupController/LNPopupController/Private/LNChevronView.m
+++ b/LNPopupController/LNPopupController/Private/LNChevronView.m
@@ -1,8 +1,8 @@
//
// LNChevronView.m
//
-// Created by Leo Natan on 16/9/16.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2016-12-02.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNChevronView.h"
diff --git a/LNPopupController/LNPopupController/Private/LNForwardingDelegate.h b/LNPopupController/LNPopupController/Private/LNForwardingDelegate.h
index e41c095..d8d7fba 100644
--- a/LNPopupController/LNPopupController/Private/LNForwardingDelegate.h
+++ b/LNPopupController/LNPopupController/Private/LNForwardingDelegate.h
@@ -2,8 +2,8 @@
// LNForwardingDelegate.h
// LNPopupController
//
-// Created by Leo Natan on 15/07/2017.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2017-07-15.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
@@ -12,4 +12,6 @@
@property (nonatomic, weak) id forwardedDelegate;
++ (BOOL)isCallerUIKit:(NSArray*)callStackReturnAddresses;
+
@end
diff --git a/LNPopupController/LNPopupController/Private/LNForwardingDelegate.m b/LNPopupController/LNPopupController/Private/LNForwardingDelegate.m
index 65e7448..6884f6c 100644
--- a/LNPopupController/LNPopupController/Private/LNForwardingDelegate.m
+++ b/LNPopupController/LNPopupController/Private/LNForwardingDelegate.m
@@ -2,11 +2,12 @@
// LNForwardingDelegate.m
// LNPopupController
//
-// Created by Leo Natan on 15/07/2017.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2017-07-15.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNForwardingDelegate.h"
+#import "_LNPopupAddressInfo.h"
@implementation LNForwardingDelegate
@@ -37,4 +38,12 @@
return [self.forwardedDelegate methodSignatureForSelector:aSelector];
}
++ (BOOL)isCallerUIKit:(NSArray *)callStackReturnAddresses
+{
+ NSUInteger addr = [callStackReturnAddresses[1] unsignedIntegerValue];
+ _LNPopupAddressInfo* addrInfo = [[_LNPopupAddressInfo alloc] initWithAddress:addr];
+
+ return [addrInfo.image hasPrefix:@"UIKit"];
+}
+
@end
diff --git a/LNPopupController/LNPopupController/Private/LNMath.h b/LNPopupController/LNPopupController/Private/LNMath.h
index b2e2cbc..4dd2556 100644
--- a/LNPopupController/LNPopupController/Private/LNMath.h
+++ b/LNPopupController/LNPopupController/Private/LNMath.h
@@ -2,13 +2,19 @@
// LNMath.h
// LNPopupController
//
-// Created by Leo Natan on 8/6/21.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-08-06.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#ifndef C__Math_h
#define C__Math_h
+#import
+
+CF_EXTERN_C_BEGIN
+
extern double _ln_clamp(double v, double lo, double hi);
+CF_EXTERN_C_END
+
#endif /* C__Math_h */
diff --git a/LNPopupController/LNPopupController/Private/LNMath.m b/LNPopupController/LNPopupController/Private/LNMath.m
index 258c2fb..07dd4af 100644
--- a/LNPopupController/LNPopupController/Private/LNMath.m
+++ b/LNPopupController/LNPopupController/Private/LNMath.m
@@ -2,8 +2,8 @@
// LNMath.
// LNPopupController
//
-// Created by Leo Natan on 8/6/21.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-08-11.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#include "LNMath.h"
diff --git a/LNPopupController/LNPopupController/Private/LNPopupBar+Private.h b/LNPopupController/LNPopupController/Private/LNPopupBar+Private.h
index 1aca446..c192aeb 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupBar+Private.h
+++ b/LNPopupController/LNPopupController/Private/LNPopupBar+Private.h
@@ -2,8 +2,8 @@
// LNPopupBar+Private.h
// LNPopupController
//
-// Created by Leo Natan on 7/25/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
@@ -13,6 +13,8 @@
#import "_LNPopupBackgroundShadowView.h"
#import "_LNPopupBarBackgroundMaskView.h"
+CF_EXTERN_C_BEGIN
+
extern const CGFloat LNPopupBarHeightCompact;
extern const CGFloat LNPopupBarHeightProminent;
extern const CGFloat LNPopupBarHeightFloating;
@@ -39,6 +41,7 @@ inline __attribute__((always_inline)) LNPopupBarStyle _LNPopupResolveBarStyleFro
- (void)_traitCollectionForPopupBarDidChange:(LNPopupBar*)bar;
- (void)_popupBarMetricsDidChange:(LNPopupBar*)bar;
+- (void)_popupBarMetricsDidChange:(LNPopupBar*)bar shouldLayout:(BOOL)layout;
- (void)_popupBarStyleDidChange:(LNPopupBar*)bar;
- (void)_popupBar:(LNPopupBar*)bar updateCustomBarController:(LNPopupCustomBarViewController*)customController cleanup:(BOOL)cleanup;
- (void)_removeInteractionGestureForPopupBar:(LNPopupBar*)bar;
@@ -64,7 +67,7 @@ inline __attribute__((always_inline)) LNPopupBarStyle _LNPopupResolveBarStyleFro
@property (nonatomic, readonly, strong) LNPopupBarAppearance* activeAppearance API_AVAILABLE(ios(13.0));
@property (nonatomic, readonly, strong) LNPopupBarAppearanceChainProxy* activeAppearanceChain API_AVAILABLE(ios(13.0));
-- (void)_recalcActiveAppearanceChain API_AVAILABLE(ios(13.0));
+- (void)_recalcActiveAppearanceChain;
@property (nonatomic, strong) UIImageView* shadowView;
@property (nonatomic, strong) UIImageView* bottomShadowView;
@@ -77,6 +80,8 @@ inline __attribute__((always_inline)) LNPopupBarStyle _LNPopupResolveBarStyleFro
@property (nonatomic, copy) NSAttributedString* attributedTitle;
@property (nonatomic, copy) NSAttributedString* attributedSubtitle;
+@property (nonatomic) NSDirectionalEdgeInsets _hackyMargins;
+
@property (nonatomic, strong) UIImage* image;
@property (nonatomic, strong) UIView* highlightView;
@@ -113,11 +118,11 @@ inline __attribute__((always_inline)) LNPopupBarStyle _LNPopupResolveBarStyleFro
@property (nonatomic) BOOL acceptsSizing;
-@property (nonatomic) BOOL _applySwiftUILayoutFixes;
+@property (nonatomic) BOOL _applySwiftUILayoutFixes API_AVAILABLE(ios(13.0));
-@property (nonatomic, strong) UIFont* swiftuiInheritedFont;
+@property (nonatomic, strong) UIFont* swiftuiInheritedFont API_AVAILABLE(ios(13.0));
-@property (nonatomic, strong) UIView* swiftuiTitleContentView;
+@property (nonatomic, strong) UIView* swiftuiTitleContentView API_AVAILABLE(ios(13.0));
@property (nonatomic, strong) UIViewController* swiftuiImageController API_AVAILABLE(ios(13.0));
@property (nonatomic, strong) UIViewController* swiftuiHiddenLeadingController API_AVAILABLE(ios(13.0));
@@ -135,4 +140,9 @@ inline __attribute__((always_inline)) LNPopupBarStyle _LNPopupResolveBarStyleFro
- (void)_appearanceDidChange;
++ (BOOL)isCatalystApp;
+- (BOOL)isWidePad;
+
@end
+
+CF_EXTERN_C_END
diff --git a/LNPopupController/LNPopupController/Private/LNPopupBar.m b/LNPopupController/LNPopupController/Private/LNPopupBar.mm
similarity index 76%
rename from LNPopupController/LNPopupController/Private/LNPopupBar.m
rename to LNPopupController/LNPopupController/Private/LNPopupBar.mm
index 64d53c8..5fe6414 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupBar.m
+++ b/LNPopupController/LNPopupController/Private/LNPopupBar.mm
@@ -2,38 +2,22 @@
// LNPopupBar.m
// LNPopupController
//
-// Created by Leo Natan on 7/24/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNPopupBar+Private.h"
#import "LNPopupCustomBarViewController+Private.h"
#import "MarqueeLabel.h"
#import "_LNPopupSwizzlingUtils.h"
+#import "_LNPopupBase64Utils.hh"
#import "NSAttributedString+LNPopupSupport.h"
-#import "_LNPopupBarShadowedImageView.h"
+#import "LNPopupImageView+Private.h"
+#import "UIView+LNPopupSupportPrivate.h"
#import "_LNPopupBarAppearanceLegacySupport.h"
#ifdef DEBUG
-static NSUserDefaults* __LNDebugUserDefaults(void)
-{
- static NSUserDefaults* rv = nil;
-
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- SEL sel = NSSelectorFromString(@"settingDefaults");
- if([NSUserDefaults respondsToSelector:sel])
- {
- rv = [NSUserDefaults valueForKey:@"settingDefaults"];
- }
- else
- {
- rv = NSUserDefaults.standardUserDefaults;
- }
- });
-
- return rv;
-}
+#import "LNPopupDebug.h"
static BOOL _LNEnableBarLayoutDebug(void)
{
@@ -66,6 +50,11 @@ CGFloat _LNPopupBarHeightForPopupBar(LNPopupBar* popupBar)
});
additionalHeight = [additionalHeightMapping[popupBar.traitCollection.preferredContentSizeCategory] doubleValue];
+ if(popupBar.effectiveBarStyle == LNPopupBarStyleFloating && popupBar.isWidePad)
+ {
+ additionalHeight += 8;
+ }
+
switch(popupBar.resolvedStyle)
{
case LNPopupBarStyleCompact:
@@ -78,17 +67,15 @@ CGFloat _LNPopupBarHeightForPopupBar(LNPopupBar* popupBar)
}
#ifndef LNPopupControllerEnforceStrictClean
-//_effectWithStyle:tintColor:invertAutomaticStyle:
-static NSString* const _eWSti = @"X2VmZmVjdFdpdGhTdHlsZTp0aW50Q29sb3I6aW52ZXJ0QXV0b21hdGljU3R5bGU6";
static SEL _effectWithStyle_tintColor_invertAutomaticStyle_SEL;
static id(*_effectWithStyle_tintColor_invertAutomaticStyle)(id, SEL, NSUInteger, UIColor*, BOOL);
__attribute__((constructor))
static void __setupFunction(void)
{
- _effectWithStyle_tintColor_invertAutomaticStyle_SEL = NSSelectorFromString(_LNPopupDecodeBase64String(_eWSti));
+ _effectWithStyle_tintColor_invertAutomaticStyle_SEL = NSSelectorFromString(LNPopupHiddenString("_effectWithStyle:tintColor:invertAutomaticStyle:"));
Method m = class_getClassMethod(UIBlurEffect.class, _effectWithStyle_tintColor_invertAutomaticStyle_SEL);
- _effectWithStyle_tintColor_invertAutomaticStyle = (void*)method_getImplementation(m);
+ _effectWithStyle_tintColor_invertAutomaticStyle = reinterpret_cast(method_getImplementation(m));
}
#endif
@@ -98,8 +85,80 @@ static void __setupFunction(void)
@interface _LNPopupBarTitlesView : UIStackView @end
@implementation _LNPopupBarTitlesView @end
+@interface _LNPopupTitleLabelWrapper: UIView
+
+@property (nonatomic, strong) UILabel* wrapped;
+@property (nonatomic, strong) NSLayoutConstraint* wrappedWidthConstraint;
+
+@end
+
+@implementation _LNPopupTitleLabelWrapper
+
++ (instancetype)wrapperForLabel:(UILabel*)wrapped
+{
+ _LNPopupTitleLabelWrapper* rv = [[_LNPopupTitleLabelWrapper alloc] initWithFrame:wrapped.frame];
+ rv.wrapped = wrapped;
+
+ rv.translatesAutoresizingMaskIntoConstraints = wrapped.translatesAutoresizingMaskIntoConstraints;
+ [rv addSubview:wrapped];
+
+ rv.wrappedWidthConstraint = [wrapped.widthAnchor constraintEqualToConstant:rv.bounds.size.width];
+
+ [NSLayoutConstraint activateConstraints:@[
+ [rv.leadingAnchor constraintEqualToAnchor:wrapped.leadingAnchor],
+ [rv.heightAnchor constraintEqualToAnchor:wrapped.heightAnchor],
+ rv->_wrappedWidthConstraint
+ ]];
+
+ return rv;
+}
+
+- (void)setBounds:(CGRect)bounds
+{
+ [super setBounds:bounds];
+
+ if(_wrappedWidthConstraint.constant == bounds.size.width)
+ {
+ return;
+ }
+
+ if(UIView.inheritedAnimationDuration == 0.0)
+ {
+ _wrappedWidthConstraint.constant = bounds.size.width;
+ [_wrapped layoutSubviews];
+ }
+ else
+ {
+ [UIView transitionWithView:_wrapped
+ duration:UIView.inheritedAnimationDuration / 2.0
+ options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionCurveEaseOut
+ animations:^{
+ _wrappedWidthConstraint.constant = bounds.size.width;
+ [_wrapped layoutSubviews];
+ } completion:nil];
+ }
+}
+
+@end
+
@interface _LNPopupBarShadowView : UIImageView @end
-@implementation _LNPopupBarShadowView @end
+@implementation _LNPopupBarShadowView
+
+#if DEBUG
+
+- (void)setAlpha:(CGFloat)alpha
+{
+ [super setAlpha:alpha];
+}
+
+- (void)setHidden:(BOOL)hidden
+{
+ [super setHidden:hidden];
+}
+
+#endif
+
+@end
@protocol _LNPopupToolbarLayoutDelegate
@@ -118,12 +177,9 @@ static void __setupFunction(void)
{
UIView* rv = [super hitTest:point withEvent:event];
- if(rv != nil && rv != self)
+ if(rv != nil && [rv isKindOfClass:UIControl.class] == NO && [NSStringFromClass(rv.class) containsString:@"BarItemView"] == NO)
{
- CGRect frameInBarCoords = [self convertRect:rv.bounds fromView:rv];
- CGRect instetFrame = CGRectInset(frameInBarCoords, 2, 0);
-
- return CGRectContainsPoint(instetFrame, point) ? rv : self;
+ rv = nil;
}
return rv;
@@ -136,8 +192,6 @@ static void __setupFunction(void)
//On iOS 11 and above reset the semantic content attribute to make sure it propagades to all subviews.
[self setSemanticContentAttribute:self.semanticContentAttribute];
- [self.subviews.firstObject setAlpha:0.0];
-
[self._layoutDelegate _toolbarDidLayoutSubviews];
}
@@ -165,7 +219,7 @@ static void __setupFunction(void)
@end
-@protocol __MarqueeLabelType
+@protocol LNMarqueeLabel
- (void)resetLabel;
- (void)unpauseLabel;
@@ -182,8 +236,8 @@ static void __setupFunction(void)
@end
-@interface __FakeMarqueeLabel : UILabel <__MarqueeLabelType> @end
-@implementation __FakeMarqueeLabel
+@interface LNNonMarqueeLabel : UILabel @end
+@implementation LNNonMarqueeLabel
- (void)resetLabel {}
- (void)unpauseLabel {}
@@ -197,13 +251,15 @@ static void __setupFunction(void)
@end
-@interface MarqueeLabel () <__MarqueeLabelType> @end
+@interface MarqueeLabel () @end
const CGFloat LNPopupBarHeightCompact = 40.0;
const CGFloat LNPopupBarHeightProminent = 64.0;
const CGFloat LNPopupBarHeightFloating = 64.0;
const CGFloat LNPopupBarProminentImageWidth = 48.0;
const CGFloat LNPopupBarFloatingImageWidth = 40.0;
+const CGFloat LNPopupBarFloatingPadImageWidth = 44.0;
+const CGFloat LNPopupBarFloatingPadWidthLimit = 954.0;
static BOOL __animatesItemSetter = NO;
@@ -223,14 +279,14 @@ __attribute__((objc_direct_members))
{
BOOL _delaysBarButtonItemLayout;
- _LNPopupBarShadowedImageView* _imageView;
+ LNPopupImageView* _imageView;
_LNPopupBarTitlesView* _titlesView;
NSLayoutConstraint* _titlesViewLeadingConstraint;
NSLayoutConstraint* _titlesViewTrailingConstraint;
- UILabel<__MarqueeLabelType>* _titleLabel;
- UILabel<__MarqueeLabelType>* _subtitleLabel;
+ UILabel* _titleLabel;
+ UILabel* _subtitleLabel;
BOOL _needsLabelsLayout;
BOOL _marqueePaused;
@@ -304,7 +360,7 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
[self _layoutBarButtonItems];
_needsLabelsLayout = YES;
- if (@available(iOS 13.0, *))
+ if(@available(iOS 13.0, *))
{
[self _fixupSwiftUIControllersWithBarStyle];
}
@@ -319,6 +375,14 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
}
}
+- (void)set_hackyMargins:(NSDirectionalEdgeInsets)_hackyMargins
+{
+ __hackyMargins = _hackyMargins;
+
+ [self _setNeedsTitleLayoutRemovingLabels:NO];
+ [self setNeedsLayout];
+}
+
- (LNPopupBarStyle)effectiveBarStyle
{
return _resolvedStyle;
@@ -347,14 +411,10 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
self.preservesSuperviewLayoutMargins = YES;
self.clipsToBounds = NO;
- if (@available(iOS 13.4, *))
- {
- UIPointerInteraction* pointerInteraction = [[UIPointerInteraction alloc] initWithDelegate:self];
- [self addInteraction:pointerInteraction];
- }
+ self.limitFloatingContentWidth = YES;
_inheritsAppearanceFromDockingView = YES;
- if (@available(iOS 13.0, *))
+ if(@available(iOS 13.0, *))
{
_standardAppearance = [LNPopupBarAppearance new];
}
@@ -373,6 +433,12 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
_contentView.clipsToBounds = NO;
[self addSubview:_contentView];
+ if(@available(iOS 13.4, *))
+ {
+ UIPointerInteraction* pointerInteraction = [[UIPointerInteraction alloc] initWithDelegate:self];
+ [_contentView addInteraction:pointerInteraction];
+ }
+
_contentMaskView = [UIView new];
_contentMaskView.backgroundColor = UIColor.whiteColor;
_contentMaskView.frame = self.bounds;
@@ -392,37 +458,26 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
_toolbar = [[_LNPopupToolbar alloc] initWithFrame:CGRectMake(0, 0, 400, 44)];
_toolbar._layoutDelegate = self;
- if (@available(iOS 13.0, *))
+ if(@available(iOS 13.0, *))
{
[_toolbar.standardAppearance configureWithTransparentBackground];
+
+#if DEBUG
+ if(_LNEnableBarLayoutDebug())
+ {
+ _toolbar.standardAppearance.backgroundColor = [UIColor.yellowColor colorWithAlphaComponent:0.7];
+ _toolbar.layer.borderColor = UIColor.blackColor.CGColor;
+ _toolbar.layer.borderWidth = 1.0;
+ }
+#endif
+ _toolbar.compactAppearance = _toolbar.standardAppearance;
}
else
{
_toolbar.translucent = NO;
_toolbar.backgroundColor = UIColor.clearColor;
-
- // Fallback on earlier versions
}
-#if DEBUG
- if(_LNEnableBarLayoutDebug())
- {
- if (@available(iOS 13.0, *))
- {
- _toolbar.standardAppearance.backgroundColor = [UIColor.yellowColor colorWithAlphaComponent:0.7];
- }
- else
- {
- _toolbar.barTintColor = [UIColor.yellowColor colorWithAlphaComponent:0.7];
- }
- _toolbar.layer.borderColor = UIColor.blackColor.CGColor;
- _toolbar.layer.borderWidth = 1.0;
- }
-#endif
- if (@available(iOS 13.0, *))
- {
- _toolbar.compactAppearance = _toolbar.standardAppearance;
- }
if(@available(iOS 15.0, *))
{
_toolbar.scrollEdgeAppearance = nil;
@@ -461,16 +516,15 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
_needsLabelsLayout = YES;
- _imageView = [_LNPopupBarShadowedImageView new];
+ _imageView = [[LNPopupImageView alloc] initWithContainingPopupBar:self];;
_imageView.autoresizingMask = UIViewAutoresizingNone;
_imageView.contentMode = UIViewContentModeScaleAspectFit;
_imageView.accessibilityTraits = UIAccessibilityTraitImage;
_imageView.isAccessibilityElement = YES;
- if (@available(iOS 13.0, *))
+ if(@available(iOS 13.0, *))
{
_imageView.layer.cornerCurve = kCACornerCurveContinuous;
}
- _imageView.layer.masksToBounds = YES;
_imageView.cornerRadius = 6;
// support smart invert and therefore do not invert image view colors
@@ -496,10 +550,8 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
_wantsBackgroundCutout = YES;
- if(@available(iOS 13.0, *))
- {
- [self _recalcActiveAppearanceChain];
- }
+ [self _recalcActiveAppearanceChain];
+ [self _appearanceDidChange];
}
return self;
@@ -518,7 +570,12 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
[self _appearanceDidChange];
}
- if(previousTraitCollection.preferredContentSizeCategory == nil || UIContentSizeCategoryCompareToCategory(previousTraitCollection.preferredContentSizeCategory, self.traitCollection.preferredContentSizeCategory) != NSOrderedSame)
+ if(UIContentSizeCategoryCompareToCategory(previousTraitCollection.preferredContentSizeCategory, self.traitCollection.preferredContentSizeCategory) != NSOrderedSame)
+ {
+ [self._barDelegate _popupBarMetricsDidChange:self];
+ }
+
+ if(_LNPopupBarHeightForPopupBar(self) != self.bounds.size.height)
{
[self._barDelegate _popupBarMetricsDidChange:self];
}
@@ -556,6 +613,8 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
return;
}
+ _customBarViewController.view.autoresizingMask = UIViewAutoresizingNone;
+
CGRect frame = _contentView.bounds;
frame.size.height = _customBarViewController.preferredContentSize.height;
_customBarViewController.view.frame = frame;
@@ -585,6 +644,7 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
CGFloat barHeight = _LNPopupBarHeightForPopupBar(self);
frame.size.height = barHeight;
+ frame = UIEdgeInsetsInsetRect(frame, _LNEdgeInsetsFromDirectionalEdgeInsets(self, __hackyMargins));
[_backgroundView setFrame:frame];
_backgroundView.layer.mask.frame = _backgroundView.bounds;
@@ -592,13 +652,20 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
BOOL isFloating = _resolvedStyle == LNPopupBarStyleFloating;
BOOL isProminent = _resolvedStyle == LNPopupBarStyleProminent;
BOOL isCustom = _resolvedStyle == LNPopupBarStyleCustom;
- BOOL isRTL = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft;
+ BOOL isRTL = self.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft;
CGRect contentFrame;
if(isFloating)
{
- CGFloat inset = self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular ? 30 : 12;
- contentFrame = CGRectOffset(UIEdgeInsetsInsetRect(frame, UIEdgeInsetsMake(4, MAX(self.safeAreaInsets.left + 12, inset), 4, MAX(self.safeAreaInsets.right + 12, inset))), 0, -2);
+ CGFloat inset = self.limitFloatingContentWidth || self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact ? 12 : 30;
+ contentFrame = UIEdgeInsetsInsetRect(frame, UIEdgeInsetsMake(4, MAX(self.safeAreaInsets.left + 12, inset), 4, MAX(self.safeAreaInsets.right + 12, inset)));
+ if(self.limitFloatingContentWidth == YES && contentFrame.size.width > LNPopupBarFloatingPadWidthLimit && UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad)
+ {
+ //On iPadOS, constrain floating bar width to 818pt.
+ CGFloat d = (contentFrame.size.width - LNPopupBarFloatingPadWidthLimit) / 2;
+ contentFrame = UIEdgeInsetsInsetRect(contentFrame, UIEdgeInsetsMake(0, d, 0, d));
+ }
+ contentFrame = CGRectOffset(contentFrame, 0, -2);
_contentView.cornerRadius = 14;
@@ -685,10 +752,6 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
[_contentView.contentView insertSubview:_imageView aboveSubview:_toolbar];
[_contentView.contentView insertSubview:_titlesView aboveSubview:_imageView];
- if(_customBarViewController != nil)
- {
- [_contentView.contentView insertSubview:_customBarViewController.view aboveSubview:_bottomShadowView];
- }
UIScreen* screen = self.window.screen ?: UIScreen.mainScreen;
CGFloat h = 1 / screen.scale;
@@ -723,10 +786,7 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
_progressView.frame = CGRectMake(cornerRadius + offset, height - 2.5, width - 2 * cornerRadius, 1.5);
}
-
- [self _layoutTitles];
-
- CGFloat titleSpacing = 1 + (1 / MAX(1, self.window.screen.scale));
+ CGFloat titleSpacing = 1 + (1 / MAX(1, screen.scale));
if(_resolvedStyle == LNPopupBarStyleCompact)
{
titleSpacing = 0;
@@ -765,27 +825,15 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
_titlesView.spacing = titleSpacing;
- [UIView performWithoutAnimation:^{
- [self.contentView layoutIfNeeded];
- [self.contentView updateConstraintsIfNeeded];
- [_titlesView layoutIfNeeded];
- [self.contentView layoutIfNeeded];
- }];
+ [self _layoutTitles];
_inLayout = NO;
}
- (void)willMoveToWindow:(UIWindow *)newWindow
{
- static NSString* willRotate = nil;
- static NSString* didRotate = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- //UIWindowWillRotateNotification
- willRotate = _LNPopupDecodeBase64String(@"VUlXaW5kb3dXaWxsUm90YXRlTm90aWZpY2F0aW9u");
- //UIWindowDidRotateNotification
- didRotate = _LNPopupDecodeBase64String(@"VUlXaW5kb3dEaWRSb3RhdGVOb3RpZmljYXRpb24=");
- });
+ static NSString* willRotate = LNPopupHiddenString("UIWindowWillRotateNotification");
+ static NSString* didRotate = LNPopupHiddenString("UIWindowDidRotateNotification");
if(self.window)
{
@@ -822,16 +870,7 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
[self setWantsBackgroundCutout:YES allowImplicitAnimations:YES];
}
-- (NSString*)_effectGroupingIdentifierKey
-{
- static NSString* gN = @"Z3JvdXBOYW1l";
- static NSString* rv = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- rv = _LNPopupDecodeBase64String(gN);
- });
- return rv;
-}
+static NSString* __ln_effectGroupingIdentifierKey = LNPopupHiddenString("groupName");
- (void)_applyGroupingIdentifier:(NSString*)groupingIdentifier toVisualEffectView:(UIVisualEffectView*)visualEffectView
{
@@ -840,12 +879,12 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
return;
}
- if([[visualEffectView valueForKey:self._effectGroupingIdentifierKey] isEqualToString:groupingIdentifier])
+ if([[visualEffectView valueForKey:__ln_effectGroupingIdentifierKey] isEqualToString:groupingIdentifier])
{
return;
}
- [visualEffectView setValue:groupingIdentifier ?: [NSString stringWithFormat:@"<%@:%p> Backdrop Group", self.class, self] forKey:self._effectGroupingIdentifierKey];
+ [visualEffectView setValue:groupingIdentifier ?: [NSString stringWithFormat:@"<%@:%p> Backdrop Group", self.class, self] forKey:__ln_effectGroupingIdentifierKey];
}
- (void)_applyGroupingIdentifierToVisualEffectView:(UIVisualEffectView*)visualEffectView
@@ -855,7 +894,7 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
- (NSString *)effectGroupingIdentifier
{
- return [self.backgroundView.effectView valueForKey:self._effectGroupingIdentifierKey];
+ return [self.backgroundView.effectView valueForKey:__ln_effectGroupingIdentifierKey];
}
- (void)setEffectGroupingIdentifier:(NSString *)groupingIdentifier
@@ -919,7 +958,7 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
[self _recalcActiveAppearanceChain];
}
-- (void)_recalcActiveAppearanceChain
+- (void)_recalcActiveAppearanceChain API_AVAILABLE(ios(13.0))
{
NSMutableArray* chain = [NSMutableArray new];
@@ -954,10 +993,7 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
{
_popupItem = popupItem;
- if (@available(iOS 13.0, *))
- {
- [self _recalcActiveAppearanceChain];
- }
+ [self _recalcActiveAppearanceChain];
}
- (void)popupBarAppearanceDidChange:(LNPopupBarAppearance*)popupBarAppearance API_AVAILABLE(ios(13.0))
@@ -967,28 +1003,31 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
- (void)_appearanceDidChange
{
- _LNPopupBarAppearanceLegacySupport* legacySupport;
- if (@available(iOS 13.0, *)) {
- legacySupport = (id)self.activeAppearance;
- } else {
- legacySupport = [_LNPopupBarAppearanceLegacySupport new];
+ _LNPopupBarAppearanceLegacySupport* activeAppearance;
+ if(@available(iOS 13.0, *))
+ {
+ activeAppearance = (id)self.activeAppearance;
+ }
+ else
+ {
+ activeAppearance = [_LNPopupBarAppearanceLegacySupport new];
}
- _highlightView.backgroundColor = legacySupport.highlightColor;
+ _highlightView.backgroundColor = activeAppearance.highlightColor;
BOOL isFloating = _resolvedStyle == LNPopupBarStyleFloating;
if(isFloating)
{
- id effect = [legacySupport floatingBackgroundEffectForTraitCollection:self.traitCollection];
+ id effect = [activeAppearance floatingBackgroundEffectForTraitCollection:self.traitCollection];
_contentView.effect = effect;
- __auto_type floatingBackgroundColor = legacySupport.floatingBackgroundColor;
- __auto_type floatingBackgroundImage = legacySupport.floatingBackgroundImage;
+ __auto_type floatingBackgroundColor = activeAppearance.floatingBackgroundColor;
+ __auto_type floatingBackgroundImage = activeAppearance.floatingBackgroundImage;
_contentView.foregroundColor = floatingBackgroundColor;
_contentView.foregroundImage = floatingBackgroundImage;
- _contentView.foregroundImageContentMode = legacySupport.floatingBackgroundImageContentMode;
+ _contentView.foregroundImageContentMode = activeAppearance.floatingBackgroundImageContentMode;
[_contentView hideOrShowImageViewIfNecessary];
}
else
@@ -996,52 +1035,29 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
_contentView.effect = nil;
_contentView.foregroundColor = nil;
_contentView.foregroundImage = nil;
- _contentView.foregroundImageContentMode = 0;
+ _contentView.foregroundImageContentMode = (UIViewContentMode)0;
[_contentView hideOrShowImageViewIfNecessary];
}
- __auto_type backgroundColor = legacySupport.backgroundColor;
- __auto_type backgroundImage = legacySupport.backgroundImage;
+ __auto_type backgroundColor = activeAppearance.backgroundColor;
+ __auto_type backgroundImage = activeAppearance.backgroundImage;
+ _backgroundView.effect = activeAppearance.backgroundEffect;
+ _backgroundView.foregroundColor = backgroundColor;
+ _backgroundView.foregroundImage = backgroundImage;
+ _backgroundView.foregroundImageContentMode = activeAppearance.backgroundImageContentMode;
+ [_backgroundView hideOrShowImageViewIfNecessary];
if(@available(iOS 13.0, *))
{
- _backgroundView.effect = legacySupport.backgroundEffect;
- }
- else
- {
- if([_barContainingController isKindOfClass:UINavigationController.class])
- {
- UIToolbar* toolbar = [_barContainingController toolbar];
- if(toolbar.barStyle == UIBarStyleBlack)
- {
- _backgroundView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
- }
- else
- {
- _backgroundView.effect = legacySupport.backgroundEffect;
- }
- }
- else
- {
- _backgroundView.effect = legacySupport.backgroundEffect;
- }
- }
- _backgroundView.foregroundColor = backgroundColor;
- _backgroundView.foregroundImage = backgroundImage;
- _backgroundView.foregroundImageContentMode = legacySupport.backgroundImageContentMode;
- [_backgroundView hideOrShowImageViewIfNecessary];
-
- if (@available(iOS 13.0, *))
- {
- _toolbar.standardAppearance.buttonAppearance = legacySupport.buttonAppearance ?: _toolbar.standardAppearance.buttonAppearance;
- _toolbar.standardAppearance.doneButtonAppearance = legacySupport.doneButtonAppearance ?: _toolbar.standardAppearance.doneButtonAppearance;
+ _toolbar.standardAppearance.buttonAppearance = activeAppearance.buttonAppearance ?: _toolbar.standardAppearance.buttonAppearance;
+ _toolbar.standardAppearance.doneButtonAppearance = activeAppearance.doneButtonAppearance ?: _toolbar.standardAppearance.doneButtonAppearance;
}
- _shadowView.image = legacySupport.shadowImage;
- _shadowView.backgroundColor = legacySupport.shadowColor;
- _bottomShadowView.image = legacySupport.shadowImage;
- _bottomShadowView.backgroundColor = legacySupport.shadowColor;
+ _shadowView.image = activeAppearance.shadowImage;
+ _shadowView.backgroundColor = activeAppearance.shadowColor;
+ _bottomShadowView.image = activeAppearance.shadowImage;
+ _bottomShadowView.backgroundColor = activeAppearance.shadowColor;
_shadowView.hidden = _resolvedStyle == LNPopupBarStyleFloating ? YES : NO;
if(_resolvedStyle == LNPopupBarStyleFloating)
@@ -1049,13 +1065,13 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
_bottomShadowView.hidden = YES;
}
- _floatingBackgroundShadowView.shadow = legacySupport.floatingBarBackgroundShadow;
+ _floatingBackgroundShadowView.shadow = activeAppearance.floatingBarBackgroundShadow;
- _imageView.shadow = legacySupport.imageShadow;
+ _imageView.shadow = activeAppearance.imageShadow;
- if (@available(iOS 13.0, *))
+ if(@available(iOS 13.0, *))
{
- [self.customBarViewController _activeAppearanceDidChange:(id)legacySupport];
+ [self.customBarViewController _activeAppearanceDidChange:(id)activeAppearance];
}
//Recalculate labels
@@ -1121,11 +1137,14 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
if(_swiftuiImageController != nil)
{
[_swiftuiImageController.view removeFromSuperview];
+ [_swiftuiImageController removeObserver:self forKeyPath:@"preferredContentSize"];
}
_swiftuiImageController = swiftuiImageController;
if(_swiftuiImageController != nil)
{
+ [_swiftuiImageController addObserver:self forKeyPath:@"preferredContentSize" options:NSKeyValueObservingOptionNew context:NULL];
+
_swiftuiImageController.view.backgroundColor = UIColor.clearColor;
_swiftuiImageController.view.translatesAutoresizingMaskIntoConstraints = NO;
@@ -1150,8 +1169,13 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
}
_swiftuiTitleContentView = swiftuiTitleContentView;
- _swiftuiTitleContentView.backgroundColor = UIColor.clearColor;
- _swiftuiTitleContentView.translatesAutoresizingMaskIntoConstraints = NO;
+
+ if(_swiftuiTitleContentView != nil)
+ {
+ [_swiftuiTitleContentView _ln_freezeInsets];
+ _swiftuiTitleContentView.backgroundColor = UIColor.clearColor;
+ _swiftuiTitleContentView.translatesAutoresizingMaskIntoConstraints = NO;
+ }
[self _setNeedsTitleLayoutRemovingLabels:YES];
}
@@ -1188,11 +1212,18 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
_swiftHacksWindow1.hidden = YES;
_swiftHacksWindow1 = nil;
}
- _swiftHacksWindow1 = [[UIWindow alloc] initWithWindowScene:self.window.windowScene];
- _swiftHacksWindow1.frame = CGRectMake(-4000, 0, 400, 400);
- _swiftHacksWindow1.rootViewController = _swiftuiHiddenLeadingController;
- _swiftHacksWindow1.hidden = NO;
- _swiftHacksWindow1.alpha = 0.0;
+
+ if(_swiftuiHiddenLeadingController != nil)
+ {
+ [UIView performWithoutAnimation:^{
+ _swiftHacksWindow1 = [[UIWindow alloc] initWithWindowScene:self.window.windowScene];
+ _swiftHacksWindow1.frame = CGRectMake(-4000, 0, 400, 400);
+ _swiftHacksWindow1.rootViewController = _swiftuiHiddenLeadingController;
+ _swiftHacksWindow1.hidden = NO;
+ _swiftHacksWindow1.alpha = 0.0;
+ [_swiftHacksWindow1 layoutSubviews];
+ }];
+ }
[self _fixupSwiftUIControllersWithBarStyle];
}
@@ -1217,11 +1248,18 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
_swiftHacksWindow2.hidden = YES;
_swiftHacksWindow2 = nil;
}
- _swiftHacksWindow2 = [[UIWindow alloc] initWithWindowScene:self.window.windowScene];
- _swiftHacksWindow2.frame = CGRectMake(-4000, 0, 400, 400);
- _swiftHacksWindow2.rootViewController = _swiftuiHiddenTrailingController;
- _swiftHacksWindow2.hidden = NO;
- _swiftHacksWindow2.alpha = 0.0;
+
+ if(_swiftuiHiddenTrailingController != nil)
+ {
+ [UIView performWithoutAnimation:^{
+ _swiftHacksWindow2 = [[UIWindow alloc] initWithWindowScene:self.window.windowScene];
+ _swiftHacksWindow2.frame = CGRectMake(-4000, 0, 400, 400);
+ _swiftHacksWindow2.rootViewController = _swiftuiHiddenTrailingController;
+ _swiftHacksWindow2.hidden = NO;
+ _swiftHacksWindow2.alpha = 0.0;
+ [_swiftHacksWindow2 layoutSubviews];
+ }];
+ }
[self _fixupSwiftUIControllersWithBarStyle];
}
@@ -1278,30 +1316,30 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
[self setNeedsLayout];
}
-- (UILabel<__MarqueeLabelType>*)_labelWithMarqueeEnabled:(BOOL)marqueeEnabled
+- (UILabel*)_labelWithMarqueeEnabled:(BOOL)marqueeEnabled
{
- UILabel<__MarqueeLabelType>* _rv = nil;
+ UILabel* _rv = nil;
if(!marqueeEnabled)
{
- __FakeMarqueeLabel* rv = [__FakeMarqueeLabel new];
+ LNNonMarqueeLabel* rv = [LNNonMarqueeLabel new];
rv.minimumScaleFactor = 1.0;
rv.lineBreakMode = NSLineBreakByTruncatingTail;
_rv = rv;
}
else
{
- _LNPopupBarAppearanceLegacySupport* legacySupport;
- if (@available(iOS 13.0, *)) {
- legacySupport = (id)self.activeAppearance;
+ _LNPopupBarAppearanceLegacySupport* activeAppearance;
+ if(@available(iOS 13.0, *)) {
+ activeAppearance = (id)self.activeAppearance;
} else {
- legacySupport = [_LNPopupBarAppearanceLegacySupport new];
+ activeAppearance = [_LNPopupBarAppearanceLegacySupport new];
}
- MarqueeLabel* rv = [[MarqueeLabel alloc] initWithFrame:CGRectZero rate:legacySupport.marqueeScrollRate andFadeLength:10];
+ MarqueeLabel* rv = [[MarqueeLabel alloc] initWithFrame:CGRectZero rate:activeAppearance.marqueeScrollRate andFadeLength:10];
rv.leadingBuffer = 0.0;
rv.trailingBuffer = 20.0;
- rv.animationDelay = legacySupport.marqueeScrollDelay;
+ rv.animationDelay = activeAppearance.marqueeScrollDelay;
rv.marqueeType = MLContinuous;
rv.holdScrolling = YES;
_rv = rv;
@@ -1317,8 +1355,10 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
- (UIView*)_viewForBarButtonItem:(UIBarButtonItem*)barButtonItem
{
UIView* itemView = [barButtonItem valueForKey:@"view"];
- //_UITAMICAdaptorView
- if([itemView.superview isKindOfClass:NSClassFromString(_LNPopupDecodeBase64String(@"X1VJVEFNSUNBZGFwdG9yVmlldw=="))])
+
+ static NSString* adaptorView = LNPopupHiddenString("_UITAMICAdaptorView");
+
+ if([itemView.superview isKindOfClass:NSClassFromString(adaptorView)])
{
itemView = itemView.superview;
}
@@ -1401,22 +1441,27 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
- (void)_updateTitleInsetsForProminentBar:(UIEdgeInsets*)titleInsets
{
- UIUserInterfaceLayoutDirection layoutDirection = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute];
+ BOOL isRTL = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft;
UIView* leftViewLast;
UIView* rightViewFirst;
NSArray* allItems = _toolbar.items;
- if(layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight)
+ static Class systemBarButtonItemButtonClass = NSClassFromString(LNPopupHiddenString("_UIButtonBarButton"));
+ BOOL isTrailingSystem;
+
+ if(isRTL == NO)
{
[self _getLeftmostView:&rightViewFirst rightmostView:NULL fromBarButtonItems:allItems];
leftViewLast = _imageView.hidden ? nil : _imageView;
+ isTrailingSystem = [rightViewFirst isKindOfClass:systemBarButtonItemButtonClass];
}
else
{
[self _getLeftmostView:NULL rightmostView:&leftViewLast fromBarButtonItems:allItems];
rightViewFirst = _imageView.hidden ? nil : _imageView;
+ isTrailingSystem = [leftViewLast isKindOfClass:systemBarButtonItemButtonClass];
}
#if DEBUG
@@ -1444,7 +1489,7 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
}
else
{
- leftViewLastFrame.size.width -= (__applySwiftUILayoutFixes ? -8 : 8);
+ leftViewLastFrame.size.width -= (__applySwiftUILayoutFixes ? -8 : isTrailingSystem ? 8 : 0);
}
}
else
@@ -1463,7 +1508,7 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
}
else
{
- rightViewFirstFrame.origin.x += (__applySwiftUILayoutFixes ? -8 : 8);
+ rightViewFirstFrame.origin.x += (__applySwiftUILayoutFixes ? -8 : isTrailingSystem ? 8 : 0);
}
}
else
@@ -1527,9 +1572,12 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
//DO NOT CHANGE NAME! Used by LNPopupUI
- (UIColor*)_titleColor
{
- if (@available(iOS 13.0, *)) {
+ if(@available(iOS 13.0, *))
+ {
return UIColor.labelColor;
- } else {
+ }
+ else
+ {
if([_barContainingController isKindOfClass:UINavigationController.class])
{
UIToolbar* toolbar = [_barContainingController toolbar];
@@ -1588,159 +1636,166 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
//DO NOT CHANGE NAME! Used by LNPopupUI
- (UIColor*)_subtitleColor
{
- if (@available(iOS 13.0, *)) {
+ if(@available(iOS 13.0, *))
+ {
return UIColor.secondaryLabelColor;
- } else {
+ }
+ else
+ {
return UIColor.systemGrayColor;
}
}
- (void)_layoutTitles
{
- _LNPopupBarAppearanceLegacySupport* legacySupport;
- if (@available(iOS 13.0, *)) {
- legacySupport = (id)self.activeAppearance;
+ _LNPopupBarAppearanceLegacySupport* activeAppearance;
+ if(@available(iOS 13.0, *)) {
+ activeAppearance = (id)self.activeAppearance;
} else {
- legacySupport = [_LNPopupBarAppearanceLegacySupport new];
+ activeAppearance = [_LNPopupBarAppearanceLegacySupport new];
}
- UIEdgeInsets titleInsets = UIEdgeInsetsZero;
-
- if(_resolvedStyle == LNPopupBarStyleCompact)
- {
- [self _updateTitleInsetsForCompactBar:&titleInsets];
- }
- else
- {
- [self _updateTitleInsetsForProminentBar:&titleInsets];
- }
-
- if([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] == UIUserInterfaceLayoutDirectionLeftToRight)
- {
- _titlesViewLeadingConstraint.constant = titleInsets.left;
- _titlesViewTrailingConstraint.constant = titleInsets.right;
- }
- else
- {
- _titlesViewLeadingConstraint.constant = titleInsets.right;
- _titlesViewTrailingConstraint.constant = titleInsets.left;
- }
-
-#if DEBUG
- if(_LNEnableBarLayoutDebug())
- {
- _titlesView.backgroundColor = [UIColor.orangeColor colorWithAlphaComponent:0.6];
- }
-#endif
-
- BOOL reset = NO;
-
- if(_needsLabelsLayout == YES)
- {
- if(_swiftuiTitleContentView != nil)
+ void (^layoutTitles)(void) = ^{
+ UIEdgeInsets titleInsets = UIEdgeInsetsZero;
+
+ if(_resolvedStyle == LNPopupBarStyleCompact)
{
- [_titlesView addArrangedSubview:_swiftuiTitleContentView];
- [_titlesView layoutIfNeeded];
- UIView* textView = _swiftuiTitleContentView.subviews.firstObject;
- [NSLayoutConstraint activateConstraints:@[
- [_swiftuiTitleContentView.heightAnchor constraintEqualToAnchor:textView.heightAnchor],
- ]];
+ [self _updateTitleInsetsForCompactBar:&titleInsets];
}
else
{
- if(_titleLabel == nil)
- {
- _titleLabel = [self _labelWithMarqueeEnabled:legacySupport.marqueeScrollEnabled];
-#if DEBUG
- if(_LNEnableBarLayoutDebug())
- {
- _titleLabel.backgroundColor = [UIColor.redColor colorWithAlphaComponent:0.5];
- }
-#endif
- _titleLabel.textColor = self._titleColor;
- _titleLabel.font = self._titleFont;
- if(_resolvedStyle == LNPopupBarStyleCompact)
- {
- _titleLabel.textAlignment = NSTextAlignmentCenter;
- }
-
- [_titlesView addArrangedSubview:_titleLabel];
- }
-
- NSAttributedString* attr = _attributedTitle.length > 0 ? [NSAttributedString ln_attributedStringWithAttributedString:_attributedTitle defaultAttributes:legacySupport.titleTextAttributes] : nil;
- if(attr != nil && [_titleLabel.attributedText isEqualToAttributedString:attr] == NO)
- {
- _titleLabel.attributedText = attr;
- reset = YES;
- }
-
- if(_subtitleLabel == nil)
- {
- _subtitleLabel = [self _labelWithMarqueeEnabled:legacySupport.marqueeScrollEnabled];
-#if DEBUG
- if(_LNEnableBarLayoutDebug())
- {
- _subtitleLabel.backgroundColor = [UIColor.cyanColor colorWithAlphaComponent:0.5];
- }
-#endif
- _subtitleLabel.textColor = self._subtitleColor;
- _subtitleLabel.font = self._subtitleFont;
- if(_resolvedStyle == LNPopupBarStyleCompact)
- {
- _subtitleLabel.textAlignment = NSTextAlignmentCenter;
- }
-
- [_titlesView addArrangedSubview:_subtitleLabel];
- }
-
- attr = _attributedSubtitle.length > 0 ? [NSAttributedString ln_attributedStringWithAttributedString:_attributedSubtitle defaultAttributes:legacySupport.subtitleTextAttributes] : nil;
- if(attr != nil && [_subtitleLabel.attributedText isEqualToAttributedString:attr] == NO)
- {
- _subtitleLabel.attributedText = attr;
- reset = YES;
- }
+ [self _updateTitleInsetsForProminentBar:&titleInsets];
}
- if(reset)
+ if(self.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight)
{
- [_titleLabel resetLabel];
- [_subtitleLabel resetLabel];
- }
-
- if(_attributedSubtitle.length > 0)
- {
- _subtitleLabel.hidden = NO;
-
- if(_needsLabelsLayout == YES)
- {
- if([_subtitleLabel isPaused] && [_titleLabel isPaused] == NO)
- {
- [_subtitleLabel unpauseLabel];
- }
- }
+ _titlesViewLeadingConstraint.constant = titleInsets.left;
+ _titlesViewTrailingConstraint.constant = titleInsets.right;
}
else
{
- _subtitleLabel.hidden = YES;
-
- if(_needsLabelsLayout == YES)
+ _titlesViewLeadingConstraint.constant = titleInsets.right;
+ _titlesViewTrailingConstraint.constant = titleInsets.left;
+ }
+
+#if DEBUG
+ if(_LNEnableBarLayoutDebug())
+ {
+ _titlesView.backgroundColor = [UIColor.orangeColor colorWithAlphaComponent:0.6];
+ }
+#endif
+
+ BOOL reset = NO;
+
+ if(_needsLabelsLayout == YES)
+ {
+ if(_swiftuiTitleContentView != nil)
{
+ [_titleLabel.superview removeFromSuperview];
+ _titleLabel = nil;
+ [_subtitleLabel.superview removeFromSuperview];
+ _subtitleLabel = nil;
+
+ [_titlesView addArrangedSubview:_swiftuiTitleContentView];
+ [_titlesView layoutIfNeeded];
+ if(unavailable(iOS 17.0, *)) {
+ UIView* textView = _swiftuiTitleContentView.subviews.firstObject;
+ [NSLayoutConstraint activateConstraints:@[
+ [_swiftuiTitleContentView.heightAnchor constraintEqualToAnchor:textView.heightAnchor],
+ ]];
+ }
+ }
+ else
+ {
+ if(_titleLabel == nil)
+ {
+ _titleLabel = [self _labelWithMarqueeEnabled:activeAppearance.marqueeScrollEnabled];
+#if DEBUG
+ if(_LNEnableBarLayoutDebug())
+ {
+ _titleLabel.backgroundColor = [UIColor.redColor colorWithAlphaComponent:0.5];
+ }
+#endif
+ _titleLabel.textColor = self._titleColor;
+ _titleLabel.font = self._titleFont;
+ if(_resolvedStyle == LNPopupBarStyleCompact)
+ {
+ _titleLabel.textAlignment = NSTextAlignmentCenter;
+ }
+
+ [_titlesView addArrangedSubview:[_LNPopupTitleLabelWrapper wrapperForLabel:_titleLabel]];
+ }
+
+ NSAttributedString* attr = _attributedTitle.length > 0 ? [NSAttributedString ln_attributedStringWithAttributedString:_attributedTitle defaultAttributes:activeAppearance.titleTextAttributes] : nil;
+ if(attr != nil && [_titleLabel.attributedText isEqualToAttributedString:attr] == NO)
+ {
+ _titleLabel.attributedText = attr;
+ reset = YES;
+ }
+
+ if(_subtitleLabel == nil)
+ {
+ _subtitleLabel = [self _labelWithMarqueeEnabled:activeAppearance.marqueeScrollEnabled];
+#if DEBUG
+ if(_LNEnableBarLayoutDebug())
+ {
+ _subtitleLabel.backgroundColor = [UIColor.cyanColor colorWithAlphaComponent:0.5];
+ }
+#endif
+ _subtitleLabel.textColor = self._subtitleColor;
+ _subtitleLabel.font = self._subtitleFont;
+ if(_resolvedStyle == LNPopupBarStyleCompact)
+ {
+ _subtitleLabel.textAlignment = NSTextAlignmentCenter;
+ }
+
+ [_titlesView addArrangedSubview:[_LNPopupTitleLabelWrapper wrapperForLabel:_subtitleLabel]];
+ }
+
+ attr = _attributedSubtitle.length > 0 ? [NSAttributedString ln_attributedStringWithAttributedString:_attributedSubtitle defaultAttributes:activeAppearance.subtitleTextAttributes] : nil;
+ if(attr != nil && [_subtitleLabel.attributedText isEqualToAttributedString:attr] == NO)
+ {
+ _subtitleLabel.attributedText = attr;
+ reset = YES;
+ }
+ }
+
+ if(reset)
+ {
+ [_titleLabel resetLabel];
[_subtitleLabel resetLabel];
- [_subtitleLabel pauseLabel];
+ }
+
+ if(_attributedSubtitle.length > 0)
+ {
+ _subtitleLabel.hidden = NO;
+
+ if(_needsLabelsLayout == YES)
+ {
+ if([_subtitleLabel isPaused] && [_titleLabel isPaused] == NO)
+ {
+ [_subtitleLabel unpauseLabel];
+ }
+ }
+ }
+ else
+ {
+ _subtitleLabel.hidden = YES;
+
+ if(_needsLabelsLayout == YES)
+ {
+ [_subtitleLabel resetLabel];
+ [_subtitleLabel pauseLabel];
+ }
}
}
- }
- [self _updateAccessibility];
+ [self _updateAccessibility];
+
+ [self _recalculateCoordinatedMarqueeScrollIfNeeded];
+ };
- [self _recalculateCoordinatedMarqueeScrollIfNeeded];
-
- [UIView performWithoutAnimation:^{
- [_titleLabel layoutIfNeeded];
- [_subtitleLabel layoutIfNeeded];
- [_titlesView layoutIfNeeded];
- [_contentView layoutIfNeeded];
- }];
+ layoutTitles();
_needsLabelsLayout = NO;
}
@@ -1788,16 +1843,56 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
_titleLabel = nil;
_subtitleLabel = nil;
- [l1 removeFromSuperview];
- [l2 removeFromSuperview];
+ [l1.superview removeFromSuperview];
+ [l2.superview removeFromSuperview];
}
[self setNeedsLayout];
}
+static CGSize LNMakeSizeWithAspectRatioInsideSize(CGSize aspectRatio, CGSize size)
+{
+ CGFloat outerAspectRatio = size.width / size.height;
+ CGFloat fAspectRatio = aspectRatio.width / aspectRatio.height;
+
+ if(fAspectRatio < outerAspectRatio)
+ {
+ return CGSizeMake(size.height * fAspectRatio, size.height);
+ }
+ else if(fAspectRatio > outerAspectRatio)
+ {
+ return CGSizeMake(size.width, size.width / fAspectRatio);
+ }
+ else
+ {
+ return size;
+ }
+}
+
+- (CGSize)_imageViewSizeWithMaxWidth:(CGFloat)width maxHeight:(CGFloat)height
+{
+ if(_imageView.image == nil && _swiftuiImageController == nil)
+ {
+ return CGSizeMake(width, height);
+ }
+
+ if(_swiftuiImageController != nil)
+ {
+ return LNMakeSizeWithAspectRatioInsideSize(_swiftuiImageController.preferredContentSize, CGSizeMake(width, height));
+ }
+
+ if(_imageView.contentMode != UIViewContentModeScaleAspectFit)
+ {
+ return CGSizeMake(width, height);
+ }
+
+ return LNMakeSizeWithAspectRatioInsideSize(_imageView.image.size, CGSizeMake(width, height));
+}
+
- (void)_layoutImageView
{
BOOL previouslyHidden = _imageView.hidden;
+ CGSize previousSize = _imageView.bounds.size;
if(_resolvedStyle == LNPopupBarStyleCompact)
{
@@ -1812,22 +1907,31 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
UIUserInterfaceLayoutDirection layoutDirection = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute];
BOOL isFloating = _resolvedStyle == LNPopupBarStyleFloating;
- CGFloat imageSize = isFloating ? LNPopupBarFloatingImageWidth : LNPopupBarProminentImageWidth;
+ CGFloat maxImageDimention = isFloating ? LNPopupBarFloatingImageWidth : LNPopupBarProminentImageWidth;
CGFloat barHeight = _contentView.bounds.size.height;
CGFloat safeLeading = 8;
+
+ if(_resolvedStyle == LNPopupBarStyleFloating && self.isWidePad == YES)
+ {
+ safeLeading += 2;
+ maxImageDimention = LNPopupBarFloatingPadImageWidth;
+ }
+
+ CGSize imageViewSize = [self _imageViewSizeWithMaxWidth:maxImageDimention maxHeight:maxImageDimention];
+
if(layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight)
{
- _imageView.center = CGPointMake(safeLeading + imageSize / 2, barHeight / 2);
+ _imageView.center = CGPointMake(safeLeading + imageViewSize.width / 2, barHeight / 2);
}
else
{
- _imageView.center = CGPointMake(_contentView.bounds.size.width - safeLeading - imageSize / 2, barHeight / 2);
+ _imageView.center = CGPointMake(_contentView.bounds.size.width - safeLeading - imageViewSize.width / 2, barHeight / 2);
}
- _imageView.bounds = CGRectMake(0, 0, imageSize, imageSize);
+ _imageView.bounds = (CGRect){0, 0, imageViewSize};
- if(previouslyHidden != _imageView.hidden)
+ if(previouslyHidden != _imageView.hidden || CGSizeEqualToSize(previousSize, imageViewSize) == NO)
{
[self _setNeedsTitleLayoutRemovingLabels:NO];
}
@@ -1909,6 +2013,12 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
{
[self._barDelegate _popupBarMetricsDidChange:self];
}
+
+ if([keyPath isEqualToString:@"preferredContentSize"] == YES && object == _swiftuiImageController)
+ {
+ [self _layoutImageView];
+ [self _setNeedsTitleLayoutRemovingLabels:NO];
+ }
}
- (void)setCustomBarViewController:(LNPopupCustomBarViewController*)customBarViewController
@@ -1944,12 +2054,12 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
[self._barDelegate _popupBar:self updateCustomBarController:_customBarViewController cleanup:NO];
[_customBarViewController addObserver:self forKeyPath:@"preferredContentSize" options:NSKeyValueObservingOptionNew context:NULL];
- if (@available(iOS 13.0, *))
+ if(@available(iOS 13.0, *))
{
[_customBarViewController _activeAppearanceDidChange:self.activeAppearance];
}
- [self.contentView addSubview:_customBarViewController.view];
+ [_contentView.contentView insertSubview:_customBarViewController.view aboveSubview:_bottomShadowView];
if(_customBarViewController.view.translatesAutoresizingMaskIntoConstraints == NO)
{
@@ -1989,14 +2099,17 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
- (void)_recalculateCoordinatedMarqueeScrollIfNeeded
{
- _LNPopupBarAppearanceLegacySupport* legacySupport;
- if (@available(iOS 13.0, *)) {
- legacySupport = (id)self.activeAppearance;
- } else {
- legacySupport = [_LNPopupBarAppearanceLegacySupport new];
+ _LNPopupBarAppearanceLegacySupport* activeAppearance;
+ if(@available(iOS 13.0, *))
+ {
+ activeAppearance = (id)self.activeAppearance;
+ }
+ else
+ {
+ activeAppearance = [_LNPopupBarAppearanceLegacySupport new];
}
- if(legacySupport.marqueeScrollEnabled == NO)
+ if(activeAppearance.marqueeScrollEnabled == NO)
{
return;
}
@@ -2009,10 +2122,10 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
MarqueeLabel* titleLabel = (id)_titleLabel;
MarqueeLabel* subtitleLabel = (id)_subtitleLabel;
- titleLabel.animationDelay = legacySupport.marqueeScrollDelay;
- subtitleLabel.animationDelay = legacySupport.marqueeScrollDelay;
+ titleLabel.animationDelay = activeAppearance.marqueeScrollDelay;
+ subtitleLabel.animationDelay = activeAppearance.marqueeScrollDelay;
- if(legacySupport.coordinateMarqueeScroll == YES && _attributedTitle.length > 0 && _attributedSubtitle.length > 0)
+ if(activeAppearance.coordinateMarqueeScroll == YES && _attributedTitle.length > 0 && _attributedSubtitle.length > 0)
{
titleLabel.holdScrolling = YES;
subtitleLabel.holdScrolling = YES;
@@ -2089,6 +2202,41 @@ static inline __attribute__((always_inline)) LNPopupBarProgressViewStyle _LNPopu
}
}
++ (BOOL)isCatalystApp
+{
+ if(@available(iOS 13.0, *))
+ {
+ BOOL isCatalystApp = NSProcessInfo.processInfo.isMacCatalystApp;
+ if(@available(iOS 14.0, *))
+ {
+ isCatalystApp = isCatalystApp || NSProcessInfo.processInfo.iOSAppOnMac;
+ }
+
+ return isCatalystApp;
+ }
+ else
+ {
+ return NO;
+ }
+}
+
+- (BOOL)isWidePad
+{
+ if(LNPopupBar.isCatalystApp)
+ {
+ return YES;
+ }
+
+ return self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular && UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad;
+}
+
+- (void)setLimitFloatingContentWidth:(BOOL)limitFloatingContentWidth
+{
+ _limitFloatingContentWidth = limitFloatingContentWidth;
+
+ [self setNeedsLayout];
+}
+
#pragma mark UIPointerInteractionDelegate
- (nullable UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4))
diff --git a/LNPopupController/LNPopupController/Private/LNPopupBarAppearance+Private.h b/LNPopupController/LNPopupController/Private/LNPopupBarAppearance+Private.h
index 5c3e2d1..d6327a4 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupBarAppearance+Private.h
+++ b/LNPopupController/LNPopupController/Private/LNPopupBarAppearance+Private.h
@@ -2,21 +2,19 @@
// LNPopupBarAppearance+Private.h
// LNPopupController
//
-// Created by Leo Natan on 6/9/21.
-// Copyright © 2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-06-20.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
NS_ASSUME_NONNULL_BEGIN
-API_AVAILABLE(ios(13.0))
@interface _LNPopupDominantColorTrait : NSObject @end
-API_AVAILABLE(ios(13.0))
@protocol _LNPopupBarAppearanceDelegate
-- (void)popupBarAppearanceDidChange:(LNPopupBarAppearance*)popupBarAppearance;
+- (void)popupBarAppearanceDidChange:(LNPopupBarAppearance*)popupBarAppearance API_AVAILABLE(ios(13.0));
@end
diff --git a/LNPopupController/LNPopupController/Private/LNPopupBarAppearance.m b/LNPopupController/LNPopupController/Private/LNPopupBarAppearance.mm
similarity index 91%
rename from LNPopupController/LNPopupController/Private/LNPopupBarAppearance.m
rename to LNPopupController/LNPopupController/Private/LNPopupBarAppearance.mm
index 9094348..9acc94b 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupBarAppearance.m
+++ b/LNPopupController/LNPopupController/Private/LNPopupBarAppearance.mm
@@ -2,20 +2,16 @@
// LNPopupBarAppearance.m
// LNPopupController
//
-// Created by Leo Natan on 6/9/21.
-// Copyright © 2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-06-20.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNPopupBarAppearance+Private.h"
#import "_LNPopupSwizzlingUtils.h"
+#import "_LNPopupBase64Utils.hh"
static void* _LNPopupItemObservationContext = &_LNPopupItemObservationContext;
-//appearance:categoriesChanged:
-static NSString* const aCC = @"YXBwZWFyYW5jZTpjYXRlZ29yaWVzQ2hhbmdlZDo=";
-//changeObserver
-static NSString* const cO = @"Y2hhbmdlT2JzZXJ2ZXI=";
-
@implementation LNPopupBarAppearance
{
BOOL _wantsDynamicFloatingBackgroundEffect;
@@ -32,13 +28,12 @@ static NSArray* __notifiedProperties = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__notifiedProperties = _LNPopupGetPropertyNames(self, nil);
- });
-
+
#ifndef LNPopupControllerEnforceStrictClean
- //appearance:categoriesChanged:
- Method m1 = class_getInstanceMethod(self, @selector(a:cC:));
- class_addMethod(self, NSSelectorFromString(_LNPopupDecodeBase64String(aCC)), method_getImplementation(m1), method_getTypeEncoding(m1));
+ Method m1 = class_getInstanceMethod(self, @selector(a:cC:));
+ class_addMethod(self, NSSelectorFromString(LNPopupHiddenString("appearance:categoriesChanged:")), method_getImplementation(m1), method_getTypeEncoding(m1));
#endif
+ });
}
}
@@ -57,9 +52,10 @@ static NSArray* __notifiedProperties = nil;
- (void)_commonInit
{
+ static NSString* changeObserver = LNPopupHiddenString("changeObserver");
+
#ifndef LNPopupControllerEnforceStrictClean
- //changeObserver
- [self setValue:self forKey:_LNPopupDecodeBase64String(cO)];
+ [self setValue:self forKey:changeObserver];
#endif
for(NSString* key in __notifiedProperties)
@@ -70,10 +66,10 @@ static NSArray* __notifiedProperties = nil;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
- id old = change[NSKeyValueChangeOldKey];
- id new = change[NSKeyValueChangeNewKey];
+ id oldValue = change[NSKeyValueChangeOldKey];
+ id newValue = change[NSKeyValueChangeNewKey];
- if([old isEqual:new])
+ if([oldValue isEqual:newValue])
{
return;
}
@@ -181,7 +177,9 @@ static NSArray* __notifiedProperties = nil;
for(NSString* key in __notifiedProperties)
{
- rv = rv && [[self valueForKey:key] isEqual:[other valueForKey:key]];
+ id myVal = [self valueForKey:key];
+ id otherVal = [other valueForKey:key];
+ rv = rv && (myVal == otherVal || [myVal isEqual:otherVal]);
}
return rv;
@@ -314,7 +312,7 @@ static NSArray* __notifiedProperties = nil;
{
return UIColor.clearColor;
}
- }];;
+ }];
self.floatingBackgroundImage = nil;
self.floatingBackgroundEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemChromeMaterial];
_wantsDynamicFloatingBackgroundEffect = YES;
diff --git a/LNPopupController/LNPopupController/Private/LNPopupBarAppearanceChainProxy.h b/LNPopupController/LNPopupController/Private/LNPopupBarAppearanceChainProxy.h
index ac04da7..a8dd1c7 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupBarAppearanceChainProxy.h
+++ b/LNPopupController/LNPopupController/Private/LNPopupBarAppearanceChainProxy.h
@@ -2,11 +2,11 @@
// LNPopupBarAppearanceChainProxy.h
// LNPopupBarAppearanceChainProxy
//
-// Created by Leo Natan on 8/7/21.
-// Copyright © 2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-08-07.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
-@import UIKit;
+#import
#import "LNPopupBarAppearance+Private.h"
NS_ASSUME_NONNULL_BEGIN
@@ -14,9 +14,9 @@ NS_ASSUME_NONNULL_BEGIN
API_AVAILABLE(ios(13.0))
@interface LNPopupBarAppearanceChainProxy : NSObject
-@property (nonatomic, strong) NSArray* chain;
+@property (nonatomic, strong) NSArray* chain API_AVAILABLE(ios(13.0));
-- (instancetype)initWithAppearanceChain:(NSArray*)chain;
+- (instancetype)initWithAppearanceChain:(NSArray*)chain API_AVAILABLE(ios(13.0));
- (id)objectForKey:(NSString*)key;
- (BOOL)boolForKey:(NSString*)key;
- (NSUInteger)unsignedIntegerForKey:(NSString*)key;
diff --git a/LNPopupController/LNPopupController/Private/LNPopupBarAppearanceChainProxy.m b/LNPopupController/LNPopupController/Private/LNPopupBarAppearanceChainProxy.m
index 9530356..f6b79a5 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupBarAppearanceChainProxy.m
+++ b/LNPopupController/LNPopupController/Private/LNPopupBarAppearanceChainProxy.m
@@ -2,8 +2,8 @@
// LNPopupBarAppearanceChainProxy.m
// LNPopupBarAppearanceChainProxy
//
-// Created by Leo Natan on 8/7/21.
-// Copyright © 2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-08-07.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNPopupBarAppearanceChainProxy.h"
diff --git a/LNPopupController/LNPopupController/Private/LNPopupCloseButton+Private.h b/LNPopupController/LNPopupController/Private/LNPopupCloseButton+Private.h
index e26a9eb..4177d19 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupCloseButton+Private.h
+++ b/LNPopupController/LNPopupController/Private/LNPopupCloseButton+Private.h
@@ -2,8 +2,8 @@
// LNPopupCloseButton+Private.h
// LNPopupController
//
-// Created by Leo Natan on 13/11/2016.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2016-12-02.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/LNPopupCloseButton.m b/LNPopupController/LNPopupController/Private/LNPopupCloseButton.mm
similarity index 85%
rename from LNPopupController/LNPopupController/Private/LNPopupCloseButton.m
rename to LNPopupController/LNPopupController/Private/LNPopupCloseButton.mm
index 43241dc..163e072 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupCloseButton.m
+++ b/LNPopupController/LNPopupController/Private/LNPopupCloseButton.mm
@@ -2,14 +2,14 @@
// LNPopupCloseButton.m
// LNPopupController
//
-// Created by Leo Natan on 7/24/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNPopupCloseButton+Private.h"
-@import ObjectiveC;
+#import
#import "LNChevronView.h"
-#import "_LNPopupSwizzlingUtils.h"
+#import "_LNPopupBase64Utils.hh"
#import "LNPopupContentView+Private.h"
@interface LNPopupCloseButton ()
@@ -23,6 +23,8 @@
@end
+@interface LNPopupCloseButton () @end
+
__attribute__((objc_direct_members))
@implementation LNPopupCloseButton
{
@@ -36,15 +38,12 @@ __attribute__((objc_direct_members))
#ifndef LNPopupControllerEnforceStrictClean
-//_actingParentViewForGestureRecognizers
-static NSString* const _aPVFGR = @"X2FjdGluZ1BhcmVudFZpZXdGb3JHZXN0dXJlUmVjb2duaXplcnM=";
-
+ (void)load
{
@autoreleasepool
{
Method m = class_getInstanceMethod(self, @selector(_aPVFGR));
- class_addMethod(self, NSSelectorFromString(_LNPopupDecodeBase64String(_aPVFGR)), method_getImplementation(m), method_getTypeEncoding(m));
+ class_addMethod(self, NSSelectorFromString(LNPopupHiddenString("_actingParentViewForGestureRecognizers")), method_getImplementation(m), method_getTypeEncoding(m));
}
}
@@ -72,29 +71,29 @@ static NSString* const _aPVFGR = @"X2FjdGluZ1BhcmVudFZpZXdGb3JHZXN0dXJlUmVjb2dua
[self setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
- if (@available(iOS 13.4, *))
- {
- self.pointerInteractionEnabled = YES;
- self.pointerStyleProvider = ^ UIPointerStyle* (UIButton *button, UIPointerEffect *proposedEffect, UIPointerShape *proposedShape) {
- NSValue* rectValue = [proposedShape valueForKey:@"rect"];
- if(rectValue == nil)
- {
- return [UIPointerStyle styleWithEffect:proposedEffect shape:proposedShape];
- }
-
- CGRect rect = CGRectInset(rectValue.CGRectValue, -5, -5);
-
- return [UIPointerStyle styleWithEffect:proposedEffect shape:[UIPointerShape shapeWithRoundedRect:rect]];
- };
- }
-
_style = LNPopupCloseButtonStyleGrabber;
[self _setupForChevronButton];
+
+ if(@available(iOS 13.4, *))
+ {
+ self.pointerInteractionEnabled = YES;
+ self.pointerStyleProvider = ^UIPointerStyle * _Nullable(UIButton * _Nonnull button, UIPointerEffect * _Nonnull proposedEffect, UIPointerShape * _Nonnull proposedShape) {
+ UIPointerLiftEffect* effect = [UIPointerLiftEffect effectWithPreview:[[UITargetedPreview alloc] initWithView:self]];
+ UIPointerShape* shape = nil;//[UIPointerShape shapeWithRoundedRect:interaction.view.frame];
+
+ return [UIPointerStyle styleWithEffect:effect shape:shape];
+ };
+ }
}
return self;
}
+- (LNPopupCloseButtonStyle)effectiveStyle
+{
+ return self.popupContentView.effectivePopupCloseButtonStyle;
+}
+
- (void)setStyle:(LNPopupCloseButtonStyle)style
{
//This will take care of cases where the user sets LNPopupCloseButtonStyleDefault as well as close button repositioning.
@@ -171,14 +170,13 @@ static NSString* const _aPVFGR = @"X2FjdGluZ1BhcmVudFZpZXdGb3JHZXN0dXJlUmVjb2dua
- (void)_setupForCircularButton
{
UIBlurEffectStyle blurStyle;
-
- if (@available(iOS 13.0, *))
- {
+ if(@available(iOS 13.0, *)) {
blurStyle = UIBlurEffectStyleSystemChromeMaterial;
- } else {
+ }
+ else
+ {
blurStyle = UIBlurEffectStyleExtraLight;
}
-
_effectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:blurStyle]];
_effectView.userInteractionEnabled = NO;
[self addSubview:_effectView];
@@ -206,16 +204,20 @@ static NSString* const _aPVFGR = @"X2FjdGluZ1BhcmVudFZpZXdGb3JHZXN0dXJlUmVjb2dua
self.layer.shadowOffset = CGSizeMake(0, 0);
self.layer.masksToBounds = NO;
- if (@available(iOS 13.0, *)) {
- self.tintColor = [UIColor labelColor];
+ if(@available(iOS 13.0, *))
+ {
+ self.tintColor = UIColor.labelColor;
+
+ [self setTitleColor:self.tintColor forState:UIControlStateNormal];
UIImageSymbolConfiguration* config = [UIImageSymbolConfiguration configurationWithPointSize:15 weight:UIImageSymbolWeightHeavy scale:UIImageSymbolScaleSmall];
UIImage* image = [[UIImage systemImageNamed:@"chevron.down" withConfiguration:config] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[self setImage:image forState:UIControlStateNormal];
- } else {
- self.tintColor = [UIColor darkTextColor];
}
- [self setTitleColor:self.tintColor forState:UIControlStateNormal];
+ else
+ {
+ self.tintColor = UIColor.darkTextColor;
+ }
}
- (void)_didTouchDown
@@ -253,7 +255,7 @@ static NSString* const _aPVFGR = @"X2FjdGluZ1BhcmVudFZpZXdGb3JHZXN0dXJlUmVjb2dua
};
if (animated) {
- [UIView animateWithDuration:0.47 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
+ [UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
alphaBlock();
} completion:nil];
} else {
diff --git a/LNPopupController/LNPopupController/Private/LNPopupContentView+Private.h b/LNPopupController/LNPopupController/Private/LNPopupContentView+Private.h
index e0507f4..b941b1c 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupContentView+Private.h
+++ b/LNPopupController/LNPopupController/Private/LNPopupContentView+Private.h
@@ -2,8 +2,8 @@
// LNPopupContentView+Private.h
// LNPopupController
//
-// Created by Leo Natan on 8/4/20.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2020-08-04.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/LNPopupContentView.m b/LNPopupController/LNPopupController/Private/LNPopupContentView.m
index 3e7fc16..878676c 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupContentView.m
+++ b/LNPopupController/LNPopupController/Private/LNPopupContentView.m
@@ -2,8 +2,8 @@
// LNPopupContentView.m
// LNPopupController
//
-// Created by Leo Natan on 8/4/20.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2020-08-04.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNPopupController.h"
@@ -16,11 +16,14 @@ LNPopupCloseButtonStyle _LNPopupResolveCloseButtonStyleFromCloseButtonStyle(LNPo
LNPopupCloseButtonStyle rv = style;
if(rv == LNPopupCloseButtonStyleDefault)
{
-#if TARGET_OS_MACCATALYST
- rv = LNPopupCloseButtonStyleRound;
-#else
- rv = LNPopupCloseButtonStyleGrabber;
-#endif
+ if([LNPopupBar isCatalystApp])
+ {
+ rv = LNPopupCloseButtonStyleRound;
+ }
+ else
+ {
+ rv = LNPopupCloseButtonStyleGrabber;
+ }
}
return rv;
}
@@ -53,6 +56,32 @@ LNPopupCloseButtonStyle _LNPopupResolveCloseButtonStyleFromCloseButtonStyle(LNPo
_popupCloseButton = [[LNPopupCloseButton alloc] initWithContainingContentView:self];
_popupCloseButton.popupContentView = self;
+ __weak __typeof(self) weakSelf = self;
+ if(@available(iOS 13.4, *))
+ {
+ _popupCloseButton.pointerInteractionEnabled = YES;
+ _popupCloseButton.pointerStyleProvider = ^ UIPointerStyle* (UIButton *button, UIPointerEffect *proposedEffect, UIPointerShape *proposedShape) {
+ LNPopupCloseButtonStyle resolvedStyle = _LNPopupResolveCloseButtonStyleFromCloseButtonStyle(weakSelf.popupCloseButtonStyle);
+
+ if(resolvedStyle == LNPopupCloseButtonStyleRound)
+ {
+ CGRect frame = CGRectInset(weakSelf.popupCloseButton.frame, 5, 5);
+
+ return [UIPointerStyle styleWithEffect:proposedEffect shape:[UIPointerShape shapeWithPath:[UIBezierPath bezierPathWithOvalInRect:frame]]];
+ }
+
+ NSValue* rectValue = [proposedShape valueForKey:@"rect"];
+ if(rectValue == nil)
+ {
+ return [UIPointerStyle styleWithEffect:proposedEffect shape:proposedShape];
+ }
+
+ CGRect rect = CGRectInset(rectValue.CGRectValue, -5, -5);
+
+ return [UIPointerStyle styleWithEffect:proposedEffect shape:[UIPointerShape shapeWithRoundedRect:rect]];
+ };
+ }
+
[_popupCloseButton setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[_popupCloseButton setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[_popupCloseButton setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
@@ -193,6 +222,10 @@ LNPopupCloseButtonStyle _LNPopupResolveCloseButtonStyleFromCloseButtonStyle(LNPo
topConstant += layoutFrame.origin.y;
topConstant = MAX(self.popupCloseButton.style == LNPopupCloseButtonStyleRound ? 12 : 0, topConstant);
+#if TARGET_OS_MACCATALYST
+ topConstant += 20;
+#endif
+
CGFloat leadingConstant = layoutFrame.origin.x;
if(topConstant != _popupCloseButtonTopConstraint.constant || leadingConstant != _popupCloseButtonLeadingConstraint.constant)
diff --git a/LNPopupController/LNPopupController/Private/LNPopupController.h b/LNPopupController/LNPopupController/Private/LNPopupController.h
index a87db41..09cd3cd 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupController.h
+++ b/LNPopupController/LNPopupController/Private/LNPopupController.h
@@ -2,8 +2,8 @@
// LNPopupController.h
// LNPopupController
//
-// Created by Leo Natan on 7/24/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
@@ -11,10 +11,13 @@
#import "UIViewController+LNPopupSupportPrivate.h"
#import
#import "LNPopupContentView+Private.h"
+#import "LNPopupBar+Private.h"
-extern const NSUInteger _LNPopupPresentationStateTransitioning;
+CF_EXTERN_C_BEGIN
-@interface LNPopupController : NSObject
+#define _LNPopupPresentationStateTransitioning ((LNPopupPresentationState)2)
+
+@interface LNPopupController : NSObject <_LNPopupBarDelegate>
- (instancetype)initWithContainerViewController:(__kindof UIViewController*)containerController;
@@ -22,6 +25,7 @@ extern const NSUInteger _LNPopupPresentationStateTransitioning;
@property (nonatomic, strong) LNPopupBar* popupBar;
@property (nonatomic, strong, readonly) LNPopupBar* popupBarStorage;
+@property (nonatomic, strong, readonly) LNPopupBar* popupBarNoCreate;
@property (nonatomic, strong) LNPopupContentView* popupContentView;
@property (nonatomic, strong) UIScrollView* popupContentContainerView;
@@ -59,4 +63,8 @@ extern const NSUInteger _LNPopupPresentationStateTransitioning;
+ (CGFloat)_statusBarHeightForView:(UIView*)view;
+- (void)_fixupGestureRecognizer:(UIGestureRecognizer*)obj;
+
@end
+
+CF_EXTERN_C_END
diff --git a/LNPopupController/LNPopupController/Private/LNPopupController.m b/LNPopupController/LNPopupController/Private/LNPopupController.mm
similarity index 75%
rename from LNPopupController/LNPopupController/Private/LNPopupController.m
rename to LNPopupController/LNPopupController/Private/LNPopupController.mm
index 7361b0d..27cc7ce 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupController.m
+++ b/LNPopupController/LNPopupController/Private/LNPopupController.mm
@@ -2,8 +2,8 @@
// LNPopupController.m
// LNPopupController
//
-// Created by Leo Natan on 7/24/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNPopupController.h"
@@ -13,37 +13,62 @@
#import "LNPopupLongPressGestureRecognizer.h"
#import "LNPopupInteractionPanGestureRecognizer.h"
#import "_LNPopupSwizzlingUtils.h"
+#import "_LNPopupBase64Utils.hh"
#import "UIView+LNPopupSupportPrivate.h"
#import "LNPopupCustomBarViewController+Private.h"
-@import ObjectiveC;
-@import os.log;
+#import "_LNPopupTransitionView.h"
+#import "_LNPopupTransitionPreferredOpenAnimator.h"
+#import "_LNPopupTransitionGenericOpenAnimator.h"
+#import "_LNPopupTransitionGenericCloseAnimator.h"
+#import "_LNPopupTransitionPreferredCloseAnimator.h"
+
+#import
+#import
+
+#if TARGET_OS_MACCATALYST
+#import
+#endif
+
+#ifdef DEBUG
+#import "LNPopupDebug.h"
+
+static BOOL _LNEnableSlowTransitionsDebug(void)
+{
+ return [__LNDebugUserDefaults() boolForKey:@"__LNPopupEnableSlowTransitionsDebug"];
+}
+#endif
+
+CF_EXTERN_C_BEGIN
#ifndef LNPopupControllerEnforceStrictClean
//visualProvider.toolbarIsSmall
static NSString* const _vPTIS = @"dmlzdWFsUHJvdmlkZXIudG9vbGJhcklzU21hbGw=";
#endif
-#if TARGET_OS_MACCATALYST
-@import AppKit;
-#endif
-
-const NSUInteger _LNPopupPresentationStateTransitioning = 2;
-
static const CGFloat LNPopupBarGestureHeightPercentThreshold = 0.2;
-static const CGFloat LNPopupBarDeveloperPanGestureThreshold = 0;
LNPopupInteractionStyle _LNPopupResolveInteractionStyleFromInteractionStyle(LNPopupInteractionStyle style)
{
- LNPopupInteractionStyle rv = style;
- if(rv == LNPopupInteractionStyleDefault)
+ if(@available(iOS 13.0, *))
{
-#if TARGET_OS_MACCATALYST
- rv = LNPopupInteractionStyleScroll;
-#else
- rv = LNPopupInteractionStyleSnap;
-#endif
+ LNPopupInteractionStyle rv = style;
+ if(rv == LNPopupInteractionStyleDefault)
+ {
+ if([LNPopupBar isCatalystApp])
+ {
+ rv = LNPopupInteractionStyleScroll;
+ }
+ else
+ {
+ rv = LNPopupInteractionStyleSnap;
+ }
+ }
+ return rv;
+ }
+ else
+ {
+ return LNPopupInteractionStyleSnap;
}
- return rv;
}
OS_ALWAYS_INLINE
@@ -51,7 +76,7 @@ static BOOL _LNCallDelegateObjectObjectBool(UIViewController* controller, UIView
{
if([controller.popupPresentationDelegate respondsToSelector:selector])
{
- void (*msgSendObjectObjectBool)(id, SEL, id, id, BOOL) = (void*)objc_msgSend;
+ void (*msgSendObjectObjectBool)(id, SEL, id, id, BOOL) = reinterpret_cast(objc_msgSend);
msgSendObjectObjectBool(controller.popupPresentationDelegate, selector, controller, content, animated);
return YES;
}
@@ -63,7 +88,7 @@ static BOOL _LNCallDelegateObjectBool(UIViewController* controller, SEL selector
{
if([controller.popupPresentationDelegate respondsToSelector:selector])
{
- void (*msgSendObjectBool)(id, SEL, id, BOOL) = (void*)objc_msgSend;
+ void (*msgSendObjectBool)(id, SEL, id, BOOL) = reinterpret_cast(objc_msgSend);
msgSendObjectBool(controller.popupPresentationDelegate, selector, controller, animated);
return YES;
}
@@ -72,7 +97,7 @@ static BOOL _LNCallDelegateObjectBool(UIViewController* controller, SEL selector
#pragma mark Popup Controller
-@interface LNPopupController () <_LNPopupItemDelegate, _LNPopupBarDelegate>
+@interface LNPopupController () <_LNPopupItemDelegate>
- (void)_applicationDidEnterBackground;
- (void)_applicationWillEnterForeground;
@@ -175,28 +200,47 @@ __attribute__((objc_direct_members))
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
_wantsFeedbackGeneration = YES;
- if (@available(iOS 13.0, *)) {
+ if(@available(iOS 13.0, *))
+ {
_softFeedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleSoft];
_rigidFeedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleRigid];
- } else {
- // Fallback on earlier versions
}
}
return self;
}
+- (void)setBottomBar:(UIView *)bottomBar
+{
+ if(@available(iOS 17.0, *))
+ {
+ [_bottomBar.traitOverrides setObject:nil forTrait:_LNPopupBarBackgroundGroupNameOverride.class];
+ }
+
+ _bottomBar = bottomBar;
+
+ if(LNPopupBar.isCatalystApp == YES)
+ {
+ return;
+ }
+
+ if(@available(iOS 17.0, *))
+ {
+ [_bottomBar.traitOverrides setObject:self.popupBar.effectGroupingIdentifier forTrait:_LNPopupBarBackgroundGroupNameOverride.class];
+ }
+}
+
- (CGRect)_frameForOpenPopupBar
{
-// CGRect defaultFrame = [_containerController defaultFrameForBottomDockingView_internalOrDeveloper];
return CGRectMake(0, - self.popupBar.frame.size.height, _containerController.view.bounds.size.width, self.popupBar.frame.size.height);
}
- (CGRect)_frameForClosedPopupBar
{
- CGRect defaultFrame = [_containerController defaultFrameForBottomDockingView_internalOrDeveloper];
+ CGRect defaultFrame = [_containerController _defaultFrameForBottomDockingViewForPopupBar:_popupBar];
UIEdgeInsets insets = [_containerController insetsForBottomDockingView];
- return CGRectMake(0, defaultFrame.origin.y - self.popupBar.frame.size.height - insets.bottom, _containerController.view.bounds.size.width, self.popupBar.frame.size.height);
+ CGFloat offset = [_containerController _ln_popupOffsetForPopupBarStyle:_popupBar.resolvedStyle];
+ return CGRectMake(0, defaultFrame.origin.y - self.popupBar.frame.size.height - insets.bottom - offset, _containerController.view.bounds.size.width, self.popupBar.frame.size.height);
}
- (void)_repositionPopupContentMovingBottomBar:(BOOL)bottomBar animated:(BOOL)animated
@@ -224,6 +268,13 @@ __attribute__((objc_direct_members))
CGFloat fractionalHeight = MAX(heightForContent - (self.popupBar.frame.origin.y + self.popupBar.frame.size.height), 0);
contentFrame.size.height = ceil(fractionalHeight);
+ if(self.popupControllerTargetState <= LNPopupPresentationStateBarPresented)
+ {
+ CGFloat offset = [_containerController _ln_popupOffsetForPopupBarStyle:self.popupBar.effectiveBarStyle];
+ contentFrame.size.height = 0;
+ contentFrame.origin.y -= offset;
+ }
+
self.popupContentView.frame = contentFrame;
_containerController.popupContentViewController.view.frame = _containerController.view.bounds;
@@ -265,10 +316,12 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
if(state == LNPopupPresentationStateOpen)
{
targetFrame = [self _frameForOpenPopupBar];
+ self.popupContentView.popupCloseButton.alpha = 1.0;
}
else if(state == LNPopupPresentationStateBarPresented || (state == _LNPopupPresentationStateTransitioning && (_popupControllerTargetState == LNPopupPresentationStateBarHidden || _popupControllerTargetState == LNPopupPresentationStateBarPresented)))
{
targetFrame = [self _frameForClosedPopupBar];
+ self.popupContentView.popupCloseButton.alpha = 0.0;
}
else
{
@@ -276,7 +329,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
targetFrame.size = closedFrame.size;
}
- _cachedDefaultFrame = [_containerController defaultFrameForBottomDockingView_internalOrDeveloper];
+ _cachedDefaultFrame = [_containerController _defaultFrameForBottomDockingViewForPopupBar:_popupBar];
_cachedInsets = [_containerController insetsForBottomDockingView];
self.popupBar.frame = targetFrame;
@@ -284,6 +337,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
if(state != _LNPopupPresentationStateTransitioning)
{
[_containerController setNeedsStatusBarAppearanceUpdate];
+ [_containerController setNeedsUpdateOfHomeIndicatorAutoHidden];
}
[self _repositionPopupContentMovingBottomBar:_containerController._ignoringLayoutDuringTransition == NO animated:animated];
@@ -297,7 +351,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
}
[currentContentController viewWillMoveToPopupContainerContentView:self.popupContentView];
- if (@available(iOS 13.0, *))
+ if(@available(iOS 13.0, *))
{
[self.popupContentView setControllerOverrideUserInterfaceStyle:currentContentController.overrideUserInterfaceStyle];
}
@@ -329,8 +383,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
return;
}
-
- if (@available(iOS 13.0, *))
+ if(@available(iOS 13.0, *))
{
[_softFeedbackGenerator prepare];
[_softFeedbackGenerator impactOccurredWithIntensity:intensity];
@@ -344,14 +397,99 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
return;
}
- if (@available(iOS 13.0, *))
+ if(@available(iOS 13.0, *))
{
[_rigidFeedbackGenerator prepare];
[_rigidFeedbackGenerator impactOccurredWithIntensity:intensity];
}
}
-- (void)_transitionToState:(LNPopupPresentationState)state notifyDelegate:(BOOL)notifyDelegate animated:(BOOL)animated useSpringAnimation:(BOOL)spring allowPopupBarAlphaModification:(BOOL)allowBarAlpha allowFeedbackGeneration:(BOOL)allowFeedbackGeneration completion:(void(^)(void))completion
+- (BOOL)_validateViewForTransition:(UIView*)viewToValidate
+{
+ if(viewToValidate == nil)
+ {
+ return NO;
+ }
+
+ if(viewToValidate == self.popupContentView || [viewToValidate isDescendantOfView:self.popupContentView] == NO)
+ {
+ return NO;
+ }
+
+ return YES;
+}
+
+- (_LNPopupTransitionView*)_userTransitionViewForTransitionFromState:(LNPopupPresentationState)fromState toState:(LNPopupPresentationState)state userView:(out id _Nonnull __strong * _Nonnull)userView
+{
+ _LNPopupTransitionView* userTransitionView = (id)[self.currentContentController _ln_transitionViewForPopupTransitionFromPresentationState:fromState toPresentationState:state view:userView];
+
+ if(userTransitionView == nil || [userTransitionView isKindOfClass:_LNPopupTransitionView.class] == NO)
+ {
+ return nil;
+ }
+
+ if([self _validateViewForTransition:userTransitionView.sourceView] == NO)
+ {
+ return nil;
+ }
+
+ return userTransitionView;
+}
+
+- (UIView*)_supportedUserViewForTransitionFromState:(LNPopupPresentationState)fromState toState:(LNPopupPresentationState)state
+{
+ UIView* userView = [self.currentContentController viewForPopupTransitionFromPresentationState:fromState toPresentationState:state];
+
+ if([self _validateViewForTransition:userView] == NO)
+ {
+ return nil;
+ }
+
+ return userView;
+}
+
+- (void)animateOpenTransitionIfNeededWithAnimator:(UIViewPropertyAnimator*)animator userTransitionView:(_LNPopupTransitionView*)userTransitionView userViewForTransition:(UIView*)userView otherAnimations:(void(^)(void))otherAnimations
+{
+ if(userView == nil)
+ {
+ return;
+ }
+
+ _LNPopupTransitionOpenAnimator* handler;
+ if([userView conformsToProtocol:@protocol(LNPopupTransitionView)])
+ {
+ handler = [[_LNPopupTransitionPreferredOpenAnimator alloc] initWithTransitionView:userTransitionView userView:userView popupBar:self.popupBar popupContentView:self.popupContentView];
+ }
+ else
+ {
+ handler = [[_LNPopupTransitionGenericOpenAnimator alloc] initWithTransitionView:userTransitionView userView:userView popupBar:self.popupBar popupContentView:self.popupContentView];
+ }
+
+ [handler animateWithAnimator:animator otherAnimations:otherAnimations];
+}
+
+- (void)animateCloseTransitionIfNeededWithAnimator:(UIViewPropertyAnimator*)animator userTransitionView:(_LNPopupTransitionView*)userTransitionView userViewForTransition:(UIView*)userView otherAnimations:(void(^)(void))otherAnimations
+{
+ if(userView == nil)
+ {
+ return;
+ }
+
+ _LNPopupTransitionCloseAnimator* handler;
+
+ if([userView conformsToProtocol:@protocol(LNPopupTransitionView)])
+ {
+ handler = [[_LNPopupTransitionPreferredCloseAnimator alloc] initWithTransitionView:userTransitionView userView:userView popupBar:self.popupBar popupContentView:self.popupContentView currentContentController:self.currentContentController containerController:self.containerController];
+ }
+ else
+ {
+ handler = [[_LNPopupTransitionGenericCloseAnimator alloc] initWithTransitionView:userTransitionView userView:userView popupBar:self.popupBar popupContentView:self.popupContentView currentContentController:self.currentContentController containerController:self.containerController];
+ }
+
+ [handler animateWithAnimator:animator otherAnimations:otherAnimations];
+}
+
+- (void)_transitionToState:(LNPopupPresentationState)state notifyDelegate:(BOOL)notifyDelegate animated:(BOOL)animated useSpringAnimation:(BOOL)spring allowPopupBarAlphaModification:(BOOL)allowBarAlpha allowFeedbackGeneration:(BOOL)allowFeedbackGeneration forceFeedbackGenerationAtStart:(BOOL)forceFeedbackAtStart completion:(void(^)(void))completion
{
if(state == _popupControllerInternalState)
{
@@ -367,7 +505,6 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
_popupContentView.hidden = NO;
[_currentContentController _userFacing_viewWillAppear:NO];
}
-// _currentContentController.view.frame = _containerController.view.bounds;
[self.popupContentView _applyBackgroundEffectWithContentViewController:_currentContentController barEffect:(id)self.popupBar.backgroundView.effect];
@@ -378,10 +515,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
[self.popupContentView.contentView layoutIfNeeded];
if(notifyDelegate == YES && state == _LNPopupPresentationStateTransitioning)
{
- if(@available(iOS 13.0, *))
- {
- [_currentContentController _userFacing_viewIsAppearing:NO];
- }
+ [_currentContentController _userFacing_viewIsAppearing:NO];
[_currentContentController _userFacing_viewDidAppear:NO];
}
}];
@@ -435,7 +569,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
{
if(shouldNotifyDelegateWillOpen == YES)
{
- if(allowFeedbackGeneration == YES)
+ if(allowFeedbackGeneration == YES && (forceFeedbackAtStart || resolvedStyle == LNPopupInteractionStyleSnap))
{
[self _generateSoftFeedbackWithIntensity:0.9];
}
@@ -448,7 +582,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
if(shouldNotifyDelegateWillClose == YES)
{
- if(allowFeedbackGeneration == YES)
+ if(allowFeedbackGeneration == YES && (forceFeedbackAtStart || resolvedStyle == LNPopupInteractionStyleSnap))
{
[self _generateRigidFeedbackWithIntensity:0.9];
}
@@ -481,10 +615,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
if(state == LNPopupPresentationStateOpen && stateAtStart == LNPopupPresentationStateBarPresented)
{
- if(@available(iOS 13.0, *))
- {
- [_currentContentController _userFacing_viewIsAppearing:animated];
- }
+ [_currentContentController _userFacing_viewIsAppearing:animated];
}
};
@@ -509,12 +640,17 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
[self _cleanupGestureRecognizersForController:_currentContentController];
[_currentContentController.viewForPopupInteractionGestureRecognizer removeGestureRecognizer:self.popupContentView.popupInteractionGestureRecognizer];
- [self.popupBar addGestureRecognizer:self.popupContentView.popupInteractionGestureRecognizer];
+ [self.popupBar.contentView addGestureRecognizer:self.popupContentView.popupInteractionGestureRecognizer];
[self.popupBar _setTitleViewMarqueesPaused:NO];
_popupContentView.accessibilityViewIsModal = NO;
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
+
+// if(allowFeedbackGeneration == YES && (forceFeedbackAtStart == false && resolvedStyle != LNPopupInteractionStyleSnap))
+// {
+// [self _generateSoftFeedbackWithIntensity:0.8];
+// }
}
_popupControllerInternalState = state;
@@ -548,6 +684,11 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
_popupContentView.accessibilityViewIsModal = YES;
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, _popupContentView.popupCloseButton);
+// if(allowFeedbackGeneration == YES && (forceFeedbackAtStart == false && resolvedStyle != LNPopupInteractionStyleSnap))
+// {
+// [self _generateSoftFeedbackWithIntensity:0.8];
+// }
+
if(_popupControllerPublicState == LNPopupPresentationStateOpen && publicStateAtStart != _popupControllerPublicState)
{
if(_LNCallDelegateObjectObjectBool(_containerController, _currentContentController, @selector(popupPresentationController:didOpenPopupWithContentController:animated:), animated) == NO)
@@ -565,18 +706,60 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
// [self _clearRunningPopupAnimators];
- if(animated == NO)
+ _LNPopupTransitionView* transitionView;
+ UIView* userView;
+ if((self.popupBar.resolvedStyle == LNPopupBarStyleProminent || self.popupBar.resolvedStyle == LNPopupBarStyleFloating) &&
+ resolvedStyle == LNPopupInteractionStyleSnap &&
+ ((stateAtStart == LNPopupPresentationStateBarPresented && state == LNPopupPresentationStateOpen) ||
+ (state == LNPopupPresentationStateBarPresented)))
{
- [UIView performWithoutAnimation:^{
- animationBlock();
- completionBlock(UIViewAnimatingPositionEnd);
- [_currentContentController.view layoutIfNeeded];
- }];
- return;
+ transitionView = [self _userTransitionViewForTransitionFromState:publicStateAtStart toState:state userView:&userView];
+
+ if(transitionView == nil)
+ {
+ userView = (id)[self _supportedUserViewForTransitionFromState:publicStateAtStart toState:state];
+ }
}
- _runningPopupAnimation = [[UIViewPropertyAnimator alloc] initWithDuration:resolvedStyle == LNPopupInteractionStyleSnap ? 0.4 : 0.5 dampingRatio:spring ? 0.85 : 1.0 animations:animationBlock];
+ CGFloat animationDuration = resolvedStyle == LNPopupInteractionStyleSnap ? 0.5 : 0.5;
+#if DEBUG
+ if(_LNEnableSlowTransitionsDebug())
+ {
+ animationDuration = 4.0;
+ }
+#endif
+
+ _runningPopupAnimation = [[UIViewPropertyAnimator alloc] initWithDuration:animationDuration dampingRatio:spring && userView == nil ? 0.85 : 1.0 animations:nil];
_runningPopupAnimation.userInteractionEnabled = NO;
+
+ if(stateAtStart == LNPopupPresentationStateBarPresented && userView != nil)
+ {
+ [self animateOpenTransitionIfNeededWithAnimator:_runningPopupAnimation userTransitionView:transitionView userViewForTransition:userView otherAnimations:animationBlock];
+ }
+ else if(state == LNPopupPresentationStateBarPresented && userView != nil)
+ {
+ [self animateCloseTransitionIfNeededWithAnimator:_runningPopupAnimation userTransitionView:transitionView userViewForTransition:userView otherAnimations:animationBlock];
+ }
+ else
+ {
+ [_runningPopupAnimation addAnimations:animationBlock];
+
+ if(state == LNPopupPresentationStateBarPresented)
+ {
+ [_runningPopupAnimation addAnimations:^{
+ if(self.containerController._ln_shouldPopupContentAnyFadeForTransition && self.containerController._ln_shouldPopupContentViewFadeForTransition)
+ {
+ self.popupContentView.alpha = 0.0;
+ }
+ } delayFactor:0.15];
+
+ [_runningPopupAnimation addCompletion:^(UIViewAnimatingPosition finalPosition) {
+ self.popupContentView.alpha = 1.0;
+// self.currentContentController.view.alpha = 1.0;
+ }];
+ }
+ }
+
[_runningPopupAnimation addCompletion:completionBlock];
[_runningPopupAnimation addCompletion:^(UIViewAnimatingPosition finalPosition) {
_runningPopupAnimation = nil;
@@ -584,6 +767,13 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
[self _addEventQueueResumptionStep:_runningPopupAnimation];
[_runningPopupAnimation startAnimation];
+
+ if(animated == NO)
+ {
+ UIViewPropertyAnimator* retained = _runningPopupAnimation;
+ [retained stopAnimation:NO];
+ [retained finishAnimationAtPosition:UIViewAnimatingPositionEnd];
+ }
}
- (void)_popupBarLongPressGestureRecognized:(UILongPressGestureRecognizer*)lpgr
@@ -627,19 +817,20 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
- (void)_popupBarPresentationByUserPanGestureHandler_began:(UIPanGestureRecognizer*)pgr
{
-#if TARGET_OS_MACCATALYST
- UIEvent* event = self.popupBar.window._ln_currentEvent;
- if(event.type == 22 /*NSEventTypeScrollWheel*/)
+ if(LNPopupBar.isCatalystApp)
{
- return;
+ UIEvent* event = self.popupBar.window._ln_currentEvent;
+ if(event.type == 22 /*NSEventTypeScrollWheel*/)
+ {
+ return;
+ }
+
+ if(event != nil && event.type == 22)
+ {
+ return;
+ }
}
- if(event != nil && event.type == 22)
- {
- return;
- }
-#endif
-
[self _start120HzHack];
if(self.popupBar.customBarViewController != nil && self.popupBar.customBarViewController.wantsDefaultPanGestureRecognizer == NO)
@@ -665,6 +856,8 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
{
if((_popupControllerInternalState == LNPopupPresentationStateBarPresented && [pgr velocityInView:self.popupBar].y < 0))
{
+ [self _end120HzHack];
+
pgr.enabled = NO;
pgr.enabled = YES;
@@ -707,12 +900,10 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
- (void)_popupBarPresentationByUserPanGestureHandler_changed:(UIPanGestureRecognizer*)pgr
{
-#if TARGET_OS_MACCATALYST
- if(self.popupBar.window._ln_currentEvent.type == 22 /*NSEventTypeScrollWheel*/)
+ if(LNPopupBar.isCatalystApp && self.popupBar.window._ln_currentEvent.type == 22 /*NSEventTypeScrollWheel*/)
{
return;
}
-#endif
LNPopupInteractionStyle resolvedStyle = _LNPopupResolveInteractionStyleFromInteractionStyle(_containerController.popupInteractionStyle);
@@ -728,16 +919,36 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
return;
}
+ CGPoint translation = [pgr translationInView:pgr.view];
+ BOOL isVerticalPan = fabs(translation.y) > fabs(translation.x);
+
if(pgr != _popupContentView.popupInteractionGestureRecognizer)
{
UIScrollView* possibleScrollView = (id)pgr.view;
if([possibleScrollView isKindOfClass:[UIScrollView class]])
{
+ //If not scrolling only vertically, ignore the scroll view's pan gesture recognizer.
+ if(possibleScrollView._ln_scrollingOnlyVertically == NO)
+ {
+ if(isVerticalPan == NO)
+ {
+ _popupContentView.popupInteractionGestureRecognizer.enabled = NO;
+ _popupContentView.popupInteractionGestureRecognizer.enabled = YES;
+ }
+ else if(_dismissGestureStarted == YES)
+ {
+ pgr.enabled = NO;
+ pgr.enabled = YES;
+ }
+
+ return;
+ }
+
id delegate = _popupContentView.popupInteractionGestureRecognizer.delegate;
if(([delegate respondsToSelector:@selector(gestureRecognizer:shouldRequireFailureOfGestureRecognizer:)] && [delegate gestureRecognizer:_popupContentView.popupInteractionGestureRecognizer shouldRequireFailureOfGestureRecognizer:pgr] == YES) ||
([delegate respondsToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)] && [delegate gestureRecognizer:_popupContentView.popupInteractionGestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:pgr] == NO) ||
- (_dismissGestureStarted == NO && possibleScrollView.contentOffset.y > - (possibleScrollView.contentInset.top + LNPopupBarDeveloperPanGestureThreshold)))
+ (_dismissGestureStarted == NO && possibleScrollView._ln_isAtTop == NO))
{
return;
}
@@ -760,7 +971,9 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
if(_dismissGestureStarted == NO && (resolvedStyle == LNPopupInteractionStyleDrag || resolvedStyle == LNPopupInteractionStyleScroll || _popupControllerInternalState > LNPopupPresentationStateBarPresented))
{
- if(resolvedStyle != LNPopupInteractionStyleSnap)
+ BOOL allowFeedback = (_popupControllerInternalState == LNPopupPresentationStateOpen && translation.y > 0) || (_popupControllerInternalState == LNPopupPresentationStateBarPresented && translation.y < 0);
+
+ if(resolvedStyle != LNPopupInteractionStyleSnap && allowFeedback)
{
[self _generateSoftFeedbackWithIntensity:0.8];
}
@@ -775,9 +988,9 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
_stateBeforeDismissStarted = _popupControllerInternalState;
- [self _transitionToState:_LNPopupPresentationStateTransitioning notifyDelegate:YES animated:YES useSpringAnimation:NO allowPopupBarAlphaModification:YES allowFeedbackGeneration:NO completion:nil];
+ [self _transitionToState:_LNPopupPresentationStateTransitioning notifyDelegate:YES animated:YES useSpringAnimation:NO allowPopupBarAlphaModification:YES allowFeedbackGeneration:NO forceFeedbackGenerationAtStart:NO completion:nil];
- _cachedDefaultFrame = [_containerController defaultFrameForBottomDockingView_internalOrDeveloper];
+ _cachedDefaultFrame = [_containerController _defaultFrameForBottomDockingViewForPopupBar:_popupBar];
_cachedInsets = [_containerController insetsForBottomDockingView];
_cachedOpenPopupFrame = [self _frameForOpenPopupBar];
@@ -838,6 +1051,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
[UIView animateWithDuration:0.3 delay:0.0 usingSpringWithDamping:500 initialSpringVelocity:0 options:0 animations:^{
[_containerController setNeedsStatusBarAppearanceUpdate];
+ [_containerController setNeedsUpdateOfHomeIndicatorAutoHidden];
} completion:nil];
}
}
@@ -874,11 +1088,11 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
[_popupContentView.popupCloseButton _setButtonContainerStationary];
if(targetState == LNPopupPresentationStateOpen)
{
- [self openPopupAnimated:YES completion:nil];
+ [self openPopupAnimated:YES allowFeedbackGeneration:targetState != _stateBeforeDismissStarted forceFeedbackGenerationAtStart:resolvedStyle == LNPopupInteractionStyleSnap completion:nil];
}
else
{
- [self closePopupAnimated:YES completion:nil];
+ [self closePopupAnimated:YES allowFeedbackGeneration:targetState != _stateBeforeDismissStarted forceFeedbackGenerationAtStart:resolvedStyle == LNPopupInteractionStyleSnap completion:nil];
}
}
@@ -995,10 +1209,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
if(_popupControllerInternalState > LNPopupPresentationStateBarPresented)
{
- if(@available(iOS 13.0, *))
- {
- [newContentController _userFacing_viewIsAppearing:NO];
- }
+ [newContentController _userFacing_viewIsAppearing:NO];
[newContentController _userFacing_viewDidAppear:NO];
[newContentController endAppearanceTransition];
[oldContentController _userFacing_viewDidDisappear:NO];
@@ -1037,13 +1248,16 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
- (void)_configurePopupBarFromBottomBarModifyingGroupingIdentifier:(BOOL)modifyingGroupingIdentifier
{
- if(modifyingGroupingIdentifier == YES)
+ if(unavailable(iOS 17.0, *))
{
- self.popupBar.effectGroupingIdentifier = _bottomBar._ln_effectGroupingIdentifierIfAvailable;
- //Schedule one more effect identifier refresh, in case it's not yet ready at this point.
- dispatch_async(dispatch_get_main_queue(), ^{
+ if(modifyingGroupingIdentifier == YES)
+ {
self.popupBar.effectGroupingIdentifier = _bottomBar._ln_effectGroupingIdentifierIfAvailable;
- });
+ //Schedule one more effect identifier refresh, in case it's not yet ready at this point.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.popupBar.effectGroupingIdentifier = _bottomBar._ln_effectGroupingIdentifierIfAvailable;
+ });
+ }
}
if(self.popupBar.inheritsAppearanceFromDockingView == NO)
@@ -1051,27 +1265,12 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
return;
}
- UIColor* bottomBarTintColor = _bottomBar.tintColor;
- if(_bottomBar.window != nil || [_bottomBar.superview.tintColor isEqual:bottomBarTintColor] == NO)
- {
- self.popupBar.systemTintColor = bottomBarTintColor;
- }
- else
- {
- self.popupBar.systemTintColor = nil;
- }
-
- if (@available(iOS 13.0, *))
+ if(@available(iOS 13.0, *))
{
UIBarAppearance* appearanceToUse = nil;
#ifndef LNPopupControllerEnforceStrictClean
- static NSString* vPTIS = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- //visualProvider.toolbarIsSmall
- vPTIS = _LNPopupDecodeBase64String(_vPTIS);
- });
+ static NSString* vPTIS = LNPopupHiddenString("visualProvider.toolbarIsSmall");
//visualProvider.toolbarIsSmall
if([_bottomBar isKindOfClass:UIToolbar.class] && [[_bottomBar valueForKeyPath:vPTIS] boolValue] == YES)
@@ -1093,6 +1292,16 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
appearanceToUse = [(id<_LNPopupBarSupport>)_bottomBar standardAppearance];
}
+ UIColor* bottomBarTintColor = _bottomBar.tintColor;
+ if(_bottomBar.window != nil || [_bottomBar.superview.tintColor isEqual:bottomBarTintColor] == NO)
+ {
+ self.popupBar.systemTintColor = bottomBarTintColor;
+ }
+ else
+ {
+ self.popupBar.systemTintColor = nil;
+ }
+
self.popupBar.systemAppearance = appearanceToUse;
}
else
@@ -1144,7 +1353,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
}
}
-- (LNPopupBar *)popupBarStorage
+- (LNPopupBar*)popupBarStorage
{
if(_popupBar)
{
@@ -1156,19 +1365,24 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
_popupBar.barContainingController = _containerController;
_popupBar._barDelegate = self;
_popupBar.popupOpenGestureRecognizer = [[LNPopupOpenTapGestureRecognizer alloc] initWithTarget:self action:@selector(_popupBarTapGestureRecognized:)];
- [_popupBar addGestureRecognizer:_popupBar.popupOpenGestureRecognizer];
+ [_popupBar.contentView addGestureRecognizer:_popupBar.popupOpenGestureRecognizer];
_popupBar.barHighlightGestureRecognizer = [[LNPopupLongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_popupBarLongPressGestureRecognized:)];
_popupBar.barHighlightGestureRecognizer.minimumPressDuration = 0;
_popupBar.barHighlightGestureRecognizer.cancelsTouchesInView = NO;
_popupBar.barHighlightGestureRecognizer.delaysTouchesBegan = NO;
_popupBar.barHighlightGestureRecognizer.delaysTouchesEnded = NO;
- [_popupBar addGestureRecognizer:_popupBar.barHighlightGestureRecognizer];
+ [_popupBar.contentView addGestureRecognizer:_popupBar.barHighlightGestureRecognizer];
return _popupBar;
}
-- (LNPopupBar *)popupBar
+- (LNPopupBar*)popupBarNoCreate
+{
+ return _popupBar;
+}
+
+- (LNPopupBar*)popupBar
{
if(_popupControllerInternalState == LNPopupPresentationStateBarHidden)
{
@@ -1178,7 +1392,7 @@ static CGFloat __smoothstep(CGFloat a, CGFloat b, CGFloat x)
return self.popupBarStorage;
}
-- (LNPopupContentView *)popupContentView
+- (LNPopupContentView*)popupContentView
{
if(_popupContentView)
{
@@ -1215,26 +1429,38 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
}];
}
+- (void)_fixupGestureRecognizer:(UIGestureRecognizer*)obj
+{
+ if([obj isKindOfClass:[UIPanGestureRecognizer class]] && [obj.view isDescendantOfView:_currentContentController.viewForPopupInteractionGestureRecognizer] && obj != _popupContentView.popupInteractionGestureRecognizer)
+ {
+ [obj addTarget:self action:@selector(_popupBarPresentationByUserPanGestureHandler:)];
+ }
+}
+
- (void)_fixupGestureRecognizersForController:(UIViewController*)vc
{
__LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(vc.viewForPopupInteractionGestureRecognizer, ^(UIView *view) {
[view.gestureRecognizers enumerateObjectsUsingBlock:^(__kindof UIGestureRecognizer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- if([obj isKindOfClass:[UIPanGestureRecognizer class]] && obj != _popupContentView.popupInteractionGestureRecognizer)
- {
- [obj addTarget:self action:@selector(_popupBarPresentationByUserPanGestureHandler:)];
- }
+ [self _fixupGestureRecognizer:obj];
}];
});
}
+- (void)_unfixupGestureRecognizer:(UIGestureRecognizer*)obj
+{
+ if([obj isKindOfClass:[UIPanGestureRecognizer class]] && [obj.view isDescendantOfView:_currentContentController.viewForPopupInteractionGestureRecognizer] && obj != _popupContentView.popupInteractionGestureRecognizer)
+ {
+ [obj removeTarget:self action:@selector(_popupBarPresentationByUserPanGestureHandler:)];
+ }
+}
+
- (void)_cleanupGestureRecognizersForController:(UIViewController*)vc
{
- [vc.viewForPopupInteractionGestureRecognizer.gestureRecognizers enumerateObjectsUsingBlock:^(__kindof UIGestureRecognizer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- if([obj isKindOfClass:[UIPanGestureRecognizer class]] && obj != _popupContentView.popupInteractionGestureRecognizer)
- {
- [obj removeTarget:self action:@selector(_popupBarPresentationByUserPanGestureHandler:)];
- }
- }];
+ __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(vc.viewForPopupInteractionGestureRecognizer, ^(UIView *view) {
+ [view.gestureRecognizers enumerateObjectsUsingBlock:^(__kindof UIGestureRecognizer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+ [self _unfixupGestureRecognizer:obj];
+ }];
+ });
}
- (BOOL)_hasRunningAnimators
@@ -1285,15 +1511,17 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
//{
// if(_runningBarAnimation != nil)
// {
-// [_runningBarAnimation stopAnimation:NO];
-// [_runningBarAnimation finishAnimationAtPosition:UIViewAnimatingPositionCurrent];
+// UIViewPropertyAnimator* retained = _runningBarAnimation;
+// [retained stopAnimation:NO];
+// [retained finishAnimationAtPosition:UIViewAnimatingPositionCurrent];
// _runningBarAnimation = nil;
// }
//
// if(_runningBarSidecarAnimation != nil)
// {
-// [_runningBarSidecarAnimation stopAnimation:NO];
-// [_runningBarSidecarAnimation finishAnimationAtPosition:UIViewAnimatingPositionCurrent];
+// UIViewPropertyAnimator* retained = _runningBarSidecarAnimation;
+// [retained stopAnimation:NO];
+// [retained finishAnimationAtPosition:UIViewAnimatingPositionCurrent];
// _runningBarSidecarAnimation = nil;
// }
//}
@@ -1302,8 +1530,9 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
//{
// if(_runningPopupAnimation != nil)
// {
-// [_runningPopupAnimation stopAnimation:NO];
-// [_runningPopupAnimation finishAnimationAtPosition:UIViewAnimatingPositionCurrent];
+// UIViewPropertyAnimator* retained = _runningPopupAnimation;
+// [retained stopAnimation:NO];
+// [retained finishAnimationAtPosition:UIViewAnimatingPositionCurrent];
// _runningPopupAnimation = nil;
// }
//}
@@ -1338,7 +1567,7 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
}
_popupControllerTargetState = LNPopupPresentationStateBarPresented;
- _bottomBar = _containerController.bottomDockingViewForPopup_internalOrDeveloper;
+ self.bottomBar = _containerController.bottomDockingViewForPopup_internalOrDeveloper;
_bottomBar.attachedPopupController = self;
self.popupBarStorage.hidden = NO;
@@ -1346,7 +1575,7 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
[self _movePopupBarAndContentToBottomBarSuperview];
[self _configurePopupBarFromBottomBar];
- [self.popupBar addGestureRecognizer:self.popupContentView.popupInteractionGestureRecognizer];
+ [self.popupBar.contentView addGestureRecognizer:self.popupContentView.popupInteractionGestureRecognizer];
[self _setContentToState:LNPopupPresentationStateBarPresented animated:animated];
@@ -1359,7 +1588,10 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
_LNCallDelegateObjectBool(_containerController, @selector(popupPresentationControllerWillPresentPopupBar:animated:), animated);
[self.popupBar.customBarViewController _userFacing_viewWillAppear:animated];
- [_bottomBar _ln_triggerBarAppearanceRefreshIfNeededTriggeringLayout:YES];
+ if(@available(iOS 13.0, *))
+ {
+ [_bottomBar _ln_triggerBarAppearanceRefreshIfNeededTriggeringLayout:YES];
+ }
_containerController._ln_bottomBarExtension_nocreate.alpha = 1.0;
CGRect barFrame = self.popupBar.frame;
@@ -1371,12 +1603,9 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
[self.popupBar setNeedsLayout];
[self.popupBar layoutIfNeeded];
- if(@available(iOS 13.0, *))
- {
- [self.popupBar.customBarViewController _userFacing_viewIsAppearing:animated];
- }
+ [self.popupBar.customBarViewController _userFacing_viewIsAppearing:animated];
- _LNPopupSupportSetPopupInsetsForViewController(_containerController, YES, UIEdgeInsetsMake(0, 0, barFrame.size.height, 0));
+ _LNPopupSupportSetPopupInsetsForViewController(_containerController, YES, UIEdgeInsetsMake(0, 0, barFrame.size.height - [_containerController _ln_popupOffsetForPopupBarStyle:self.popupBar.resolvedStyle], 0));
if(open)
{
@@ -1387,7 +1616,7 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
_LNCallDelegateObjectBool(_containerController, @selector(popupPresentationControllerWillOpenPopup:animated:), animated);
}
- [self _openPopupAnimated:animated completion:completionBlock];
+ [self _openPopupAnimated:animated allowFeedbackGeneration:YES forceFeedbackGenerationAtStart:YES completion:completionBlock];
}
};
@@ -1437,10 +1666,7 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
_runningBarAnimation = nil;
}];
[self _addEventQueueResumptionStep:_runningBarAnimation];
- if(animated == NO)
- {
- _runningBarAnimation.fractionComplete = 1.0;
- }
+
[_runningBarAnimation startAnimation];
_runningBarSidecarAnimation = [[UIViewPropertyAnimator alloc] initWithDuration:0.3 dampingRatio:500 animations:middle];
@@ -1450,8 +1676,16 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
[self _addEventQueueResumptionStep:_runningBarSidecarAnimation];
if(animated == NO)
{
- _runningBarSidecarAnimation.fractionComplete = 1.0;
[_runningBarSidecarAnimation startAnimation];
+
+ UIViewPropertyAnimator* retained1 = _runningBarAnimation;
+ UIViewPropertyAnimator* retained2 = _runningBarSidecarAnimation;
+
+ [retained1 stopAnimation:NO];
+ [retained1 finishAnimationAtPosition:UIViewAnimatingPositionEnd];
+
+ [retained2 stopAnimation:NO];
+ [retained2 finishAnimationAtPosition:UIViewAnimatingPositionEnd];
}
else
{
@@ -1479,13 +1713,18 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
}
- (void)openPopupAnimated:(BOOL)animated completion:(void(^)(void))completionBlock
+{
+ [self openPopupAnimated:animated allowFeedbackGeneration:YES forceFeedbackGenerationAtStart:YES completion:completionBlock];
+}
+
+- (void)openPopupAnimated:(BOOL)animated allowFeedbackGeneration:(BOOL)allowFeedbackGeneration forceFeedbackGenerationAtStart:(BOOL)forceFeedbackAtStart completion:(void(^)(void))completionBlock
{
[self _enqueueEvent:[_LNPopupControllerEvent openEventWithOperation:^{
- [self _openPopupAnimated:animated completion:completionBlock];
+ [self _openPopupAnimated:animated allowFeedbackGeneration:allowFeedbackGeneration forceFeedbackGenerationAtStart:forceFeedbackAtStart completion:completionBlock];
}]];
}
-- (void)_openPopupAnimated:(BOOL)animated completion:(void(^)(void))completionBlock
+- (void)_openPopupAnimated:(BOOL)animated allowFeedbackGeneration:(BOOL)allowFeedbackGeneration forceFeedbackGenerationAtStart:(BOOL)forceFeedbackAtStart completion:(void(^)(void))completionBlock
{
[self _start120HzHack];
@@ -1493,7 +1732,7 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
{
[_containerController.view setNeedsLayout];
[_containerController.view layoutIfNeeded];
- [self _transitionToState:LNPopupPresentationStateOpen notifyDelegate:YES animated:animated useSpringAnimation:NO allowPopupBarAlphaModification:YES allowFeedbackGeneration:YES completion:completionBlock];
+ [self _transitionToState:LNPopupPresentationStateOpen notifyDelegate:YES animated:animated useSpringAnimation:NO allowPopupBarAlphaModification:YES allowFeedbackGeneration:allowFeedbackGeneration forceFeedbackGenerationAtStart:forceFeedbackAtStart completion:completionBlock];
}
else if(completionBlock != nil)
{
@@ -1502,19 +1741,24 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
}
- (void)closePopupAnimated:(BOOL)animated completion:(void(^)(void))completionBlock
+{
+ [self closePopupAnimated:animated allowFeedbackGeneration:YES forceFeedbackGenerationAtStart:YES completion:completionBlock];
+}
+
+- (void)closePopupAnimated:(BOOL)animated allowFeedbackGeneration:(BOOL)allowFeedbackGeneration forceFeedbackGenerationAtStart:(BOOL)forceFeedbackAtStart completion:(void(^)(void))completionBlock
{
[self _enqueueEvent:[_LNPopupControllerEvent closeEventWithOperation:^{
- [self _closePopupAnimated:animated completion:completionBlock];
+ [self _closePopupAnimated:animated allowFeedbackGeneration:allowFeedbackGeneration forceFeedbackGenerationAtStart:forceFeedbackAtStart completion:completionBlock];
}]];
}
-- (void)_closePopupAnimated:(BOOL)animated completion:(void(^)(void))completionBlock
+- (void)_closePopupAnimated:(BOOL)animated allowFeedbackGeneration:(BOOL)allowFeedbackGeneration forceFeedbackGenerationAtStart:(BOOL)forceFeedbackAtStart completion:(void(^)(void))completionBlock
{
if(_popupControllerTargetState != LNPopupPresentationStateBarPresented)
{
LNPopupInteractionStyle resolvedStyle = _LNPopupResolveInteractionStyleFromInteractionStyle(_containerController.popupInteractionStyle);
- [self _transitionToState:LNPopupPresentationStateBarPresented notifyDelegate:YES animated:animated useSpringAnimation:resolvedStyle == LNPopupInteractionStyleSnap ? YES : NO allowPopupBarAlphaModification:YES allowFeedbackGeneration:YES completion:completionBlock];
+ [self _transitionToState:LNPopupPresentationStateBarPresented notifyDelegate:YES animated:animated useSpringAnimation:resolvedStyle == LNPopupInteractionStyleSnap ? YES : NO allowPopupBarAlphaModification:YES allowFeedbackGeneration:allowFeedbackGeneration forceFeedbackGenerationAtStart:forceFeedbackAtStart completion:completionBlock];
}
else if(completionBlock != nil)
{
@@ -1552,10 +1796,23 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
// [self _clearRunningBarAnimators];
// [self _clearRunningPopupAnimators];
- _runningBarAnimation = [[UIViewPropertyAnimator alloc] initWithDuration:animated ? 0.5 : 0.0 dampingRatio:500 animations:^{
+ __weak decltype(self) weakSelf = self;
+
+ _runningBarAnimation = [[UIViewPropertyAnimator alloc] initWithDuration:0.5 dampingRatio:500 animations:^{
+ __strong decltype(weakSelf) self = weakSelf;
+ if(self == nil)
+ {
+ return;
+ }
+
_LNCallDelegateObjectBool(_containerController, @selector(popupPresentationControllerWillDismissPopupBar:animated:), animated);
[self.popupBar.customBarViewController _userFacing_viewWillDisappear:animated];
+ if(@available(iOS 13.0, *))
+ {
+ [_bottomBar _ln_triggerBarAppearanceRefreshIfNeededTriggeringLayout:YES];
+ }
+
CGRect barFrame = self.popupBar.frame;
barFrame.size.height = 0;
self.popupBar.frame = barFrame;
@@ -1565,8 +1822,6 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
self.popupBar.shadowView.alpha = 0.0;
_LNPopupSupportSetPopupInsetsForViewController(_containerController, YES, UIEdgeInsetsZero);
- [_bottomBar _ln_triggerBarAppearanceRefreshIfNeededTriggeringLayout:YES];
-
CGFloat currentBarAlpha = self.popupBarStorage.alpha;
[UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:500 initialSpringVelocity:0 options:UIViewAnimationOptionAllowAnimatedContent animations:^{
if(_containerController.shouldFadePopupBarOnDismiss)
@@ -1579,7 +1834,13 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
}];
}];
- [_runningBarAnimation addCompletion:^(UIViewAnimatingPosition finalPosition) {
+ [_runningBarAnimation addCompletion:^(UIViewAnimatingPosition finalPosition) {
+ __strong decltype(weakSelf) self = weakSelf;
+ if(self == nil)
+ {
+ return;
+ }
+
if(finalPosition != UIViewAnimatingPositionEnd)
{
return;
@@ -1593,7 +1854,7 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
[self _removeContentControllerFromContentView:_currentContentController];
- CGRect bottomBarFrame = [_containerController defaultFrameForBottomDockingView_internalOrDeveloper];
+ CGRect bottomBarFrame = [_containerController _defaultFrameForBottomDockingViewForPopupBar:_popupBar];
bottomBarFrame.origin.y -= _cachedInsets.bottom;
_bottomBar.frame = bottomBarFrame;
@@ -1615,7 +1876,7 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
_LNCallDelegateObjectBool(_containerController, @selector(popupPresentationControllerDidDismissPopupBar:animated:), animated);
_bottomBar.attachedPopupController = nil;
- _bottomBar = nil;
+ self.bottomBar = nil;
[self _end120HzHack];
@@ -1625,7 +1886,15 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
_runningBarAnimation = nil;
}];
[self _addEventQueueResumptionStep:_runningBarAnimation];
+
[_runningBarAnimation startAnimation];
+
+ if(animated == NO)
+ {
+ UIViewPropertyAnimator* retained = _runningBarAnimation;
+ [retained stopAnimation:NO];
+ [retained finishAnimationAtPosition:UIViewAnimatingPositionEnd];
+ }
};
_dismissalOverride = YES;
@@ -1637,7 +1906,7 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
LNPopupInteractionStyle resolvedStyle = _LNPopupResolveInteractionStyleFromInteractionStyle(_containerController.popupInteractionStyle);
- [self _transitionToState:LNPopupPresentationStateBarPresented notifyDelegate:YES animated:animated useSpringAnimation:resolvedStyle == LNPopupInteractionStyleSnap allowPopupBarAlphaModification:YES allowFeedbackGeneration:YES completion:dismissalAnimationCompletionBlock];
+ [self _transitionToState:LNPopupPresentationStateBarPresented notifyDelegate:YES animated:animated useSpringAnimation:resolvedStyle == LNPopupInteractionStyleSnap allowPopupBarAlphaModification:YES allowFeedbackGeneration:YES forceFeedbackGenerationAtStart:YES completion:dismissalAnimationCompletionBlock];
}
else
{
@@ -1673,6 +1942,11 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
}
- (void)_popupBarMetricsDidChange:(LNPopupBar*)bar
+{
+ [self _popupBarMetricsDidChange:bar shouldLayout:YES];
+}
+
+- (void)_popupBarMetricsDidChange:(LNPopupBar*)bar shouldLayout:(BOOL)layout
{
if(self.popupBar.acceptsSizing == NO)
{
@@ -1686,13 +1960,16 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
barFrame.origin.y -= (barFrame.size.height - currentHeight);
self.popupBar.frame = barFrame;
- _LNPopupSupportSetPopupInsetsForViewController(_containerController, YES, UIEdgeInsetsMake(0, 0, self.popupBar.frame.size.height, 0));
+ _LNPopupSupportSetPopupInsetsForViewController(_containerController, layout, UIEdgeInsetsMake(0, 0, self.popupBar.frame.size.height - [_containerController _ln_popupOffsetForPopupBarStyle:self.popupBar.resolvedStyle], 0));
}
- (void)_popupBarStyleDidChange:(LNPopupBar*)bar
{
[self _updateBarExtensionStyleFromPopupBar];
- [_containerController.popupBar _applyGroupingIdentifierToVisualEffectView:self.popupContentView.effectView];
+ if(LNPopupBar.isCatalystApp == NO)
+ {
+ [_containerController.popupBar _applyGroupingIdentifierToVisualEffectView:self.popupContentView.effectView];
+ }
}
- (void)_popupBar:(LNPopupBar *)bar updateCustomBarController:(LNPopupCustomBarViewController *)customController cleanup:(BOOL)cleanup
@@ -1713,7 +1990,7 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
- if (@available(iOS 15.0, *))
+ if(@available(iOS 15.0, *))
{
if(UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone && UIScreen.mainScreen.maximumFramesPerSecond > 60 && [[NSBundle.mainBundle objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"] boolValue] == NO)
{
@@ -1756,9 +2033,11 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
+ (CGFloat)_statusBarHeightForView:(UIView*)view
{
-#if TARGET_OS_MACCATALYST
- return 0;
-#else
+ if(LNPopupBar.isCatalystApp)
+ {
+ return 0;
+ }
+
if(view == nil || view.window == nil)
{
return 0;
@@ -1767,15 +2046,17 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
if(view.window.safeAreaInsets.top == 0)
{
//Probably 🤷�♂� an old iPhone
- if (@available(iOS 13.0, *)) {
+ if(@available(iOS 13.0, *))
+ {
return view.window.windowScene.statusBarManager.statusBarHidden ? 0 : 20;
- } else {
+ }
+ else
+ {
return UIApplication.sharedApplication.statusBarFrame.size.height;
}
}
return view.window.safeAreaInsets.top;
-#endif
}
- (void)_120HzTick {}
@@ -1824,10 +2105,9 @@ static void __LNPopupControllerDeeplyEnumerateSubviewsUsingBlock(UIView* view, v
- (void)_popupItem_update_standardAppearance
{
- if (@available(iOS 13.0, *))
- {
- [self.popupBarStorage _recalcActiveAppearanceChain];
- }
+ [self.popupBarStorage _recalcActiveAppearanceChain];
}
@end
+
+CF_EXTERN_C_END
diff --git a/LNPopupController/LNPopupController/Private/LNPopupCustomBarViewController+Private.h b/LNPopupController/LNPopupController/Private/LNPopupCustomBarViewController+Private.h
index 7936ebf..76a6537 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupCustomBarViewController+Private.h
+++ b/LNPopupController/LNPopupController/Private/LNPopupCustomBarViewController+Private.h
@@ -2,8 +2,8 @@
// LNPopupItem+Private.h
// LNPopupController
//
-// Created by Leo Natan on 7/25/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2016-12-30.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/LNPopupCustomBarViewController.m b/LNPopupController/LNPopupController/Private/LNPopupCustomBarViewController.m
index a9c8090..bb9cd6e 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupCustomBarViewController.m
+++ b/LNPopupController/LNPopupController/Private/LNPopupCustomBarViewController.m
@@ -2,8 +2,8 @@
// LNPopupBarContentViewController.m
// LNPopupController
//
-// Created by Leo Natan on 15/12/2016.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2016-12-30.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNPopupCustomBarViewController+Private.h"
@@ -126,7 +126,7 @@
__ln_popup_suppressViewControllerLifecycle = NO;
}
-- (void)_userFacing_viewIsAppearing:(BOOL)animated
+- (void)_userFacing_viewIsAppearing:(BOOL)animated API_AVAILABLE(ios(13.0))
{
__ln_popup_suppressViewControllerLifecycle = YES;
diff --git a/LNPopupController/LNPopupController/Private/LNPopupDebug.h b/LNPopupController/LNPopupController/Private/LNPopupDebug.h
new file mode 100644
index 0000000..55b096c
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/LNPopupDebug.h
@@ -0,0 +1,19 @@
+//
+// LNPopupDebug.h
+// LNPopupController
+//
+// Created by Léo Natan on 4/4/25.
+// Copyright © 2025 Léo Natan. All rights reserved.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+#ifdef DEBUG
+CF_EXTERN_C_BEGIN
+extern NSUserDefaults* __LNDebugUserDefaults(void);
+CF_EXTERN_C_END
+#endif
+
+NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/Private/LNPopupDebug.m b/LNPopupController/LNPopupController/Private/LNPopupDebug.m
new file mode 100644
index 0000000..28df9fe
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/LNPopupDebug.m
@@ -0,0 +1,31 @@
+//
+// LNPopupDebug.m
+// LNPopupController
+//
+// Created by Léo Natan on 4/4/25.
+// Copyright © 2025 Léo Natan. All rights reserved.
+//
+
+#import "LNPopupDebug.h"
+
+#ifdef DEBUG
+NSUserDefaults* __LNDebugUserDefaults(void)
+{
+ static NSUserDefaults* rv = nil;
+
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ SEL sel = NSSelectorFromString(@"settingDefaults");
+ if([NSUserDefaults respondsToSelector:sel])
+ {
+ rv = [NSUserDefaults valueForKey:@"settingDefaults"];
+ }
+ else
+ {
+ rv = NSUserDefaults.standardUserDefaults;
+ }
+ });
+
+ return rv;
+}
+#endif
diff --git a/LNPopupController/LNPopupController/Private/LNPopupImageView+Private.h b/LNPopupController/LNPopupController/Private/LNPopupImageView+Private.h
new file mode 100644
index 0000000..646b539
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/LNPopupImageView+Private.h
@@ -0,0 +1,20 @@
+//
+// LNPopupImageView+Private.h
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import
+#import "LNPopupBar+Private.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface LNPopupImageView ()
+
+- (instancetype)initWithContainingPopupBar:(LNPopupBar*)popupBar;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/Private/LNPopupImageView.mm b/LNPopupController/LNPopupController/Private/LNPopupImageView.mm
new file mode 100644
index 0000000..4c06cdf
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/LNPopupImageView.mm
@@ -0,0 +1,255 @@
+//
+// LNPopupImageView.mm
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import "LNPopupImageView+Private.h"
+#import "_LNPopupBase64Utils.hh"
+#import "UIViewController+LNPopupSupportPrivate.h"
+#import
+
+@interface _LNPopupBarShadowedImageViewLayer : CALayer @end
+@implementation _LNPopupBarShadowedImageViewLayer
+{
+ @public
+ __weak CALayer* _imageContentsLayer;
+}
+
+- (void)setMasksToBounds:(BOOL)masksToBounds
+{
+ [super setMasksToBounds:NO];
+}
+
+- (void)addSublayer:(CALayer *)layer
+{
+ layer.masksToBounds = YES;
+ if(@available(iOS 13.0, *))
+ {
+ layer.cornerCurve = kCACornerCurveContinuous;
+ }
+ layer.cornerRadius = _imageContentsLayer.cornerRadius;
+ [super addSublayer:layer];
+}
+
+- (void)layoutSublayers
+{
+ [super layoutSublayers];
+ _imageContentsLayer.frame = self.bounds;
+}
+
+- (void)setCornerRadius:(CGFloat)cornerRadius
+{
+ [(LNPopupImageView*)self.delegate setCornerRadius:cornerRadius];
+}
+
+- (void)setSuperCornerRadius:(CGFloat)cornerRadius
+{
+ _imageContentsLayer.cornerRadius = cornerRadius;
+ for(CALayer* sublayer in self.sublayers)
+ {
+ if(sublayer != _imageContentsLayer)
+ {
+ sublayer.cornerRadius = cornerRadius;
+ }
+ }
+}
+
+- (void)setContents:(id)contents
+{
+ [_imageContentsLayer setContents:contents];
+}
+
+- (void)setContentsRect:(CGRect)contentsRect
+{
+ [_imageContentsLayer setContentsRect:contentsRect];
+}
+
+- (void)setContentsScale:(CGFloat)contentsScale
+{
+ [_imageContentsLayer setContentsScale:contentsScale];
+}
+
+- (void)setContentsCenter:(CGRect)contentsCenter
+{
+ [_imageContentsLayer setContentsCenter:contentsCenter];
+}
+
+- (void)setContentsFormat:(CALayerContentsFormat)contentsFormat
+{
+ [_imageContentsLayer setContentsFormat:contentsFormat];
+}
+
+- (void)setContentsGravity:(CALayerContentsGravity)contentsGravity
+{
+ [_imageContentsLayer setContentsGravity:contentsGravity];
+}
+
+- (void)setWantsExtendedDynamicRangeContent:(BOOL)wantsExtendedDynamicRangeContent
+{
+ [super setWantsExtendedDynamicRangeContent:wantsExtendedDynamicRangeContent];
+ [_imageContentsLayer setWantsExtendedDynamicRangeContent:wantsExtendedDynamicRangeContent];
+}
+
+- (void)setImageContentsLayer:(CALayer*)layer
+{
+ _imageContentsLayer = layer;
+}
+
+@end
+
+@implementation LNPopupImageView
+{
+ __weak LNPopupBar* _containingBar;
+}
+
++ (Class)layerClass
+{
+ return [_LNPopupBarShadowedImageViewLayer class];
+}
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+ self = [super initWithFrame:frame];
+
+ if(self)
+ {
+ [self _commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithImage:(UIImage *)image
+{
+ return [self initWithImage:image highlightedImage:nil];
+}
+
+- (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage
+{
+ self = [super initWithImage:image highlightedImage:highlightedImage];
+
+ if(self)
+ {
+ [self _commonInit];
+ }
+
+ return self;
+}
+
+- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event
+{
+ id rv = [super actionForLayer:layer forKey:event];
+
+ return rv;
+}
+
+- (instancetype)initWithContainingPopupBar:(LNPopupBar *)popupBar
+{
+ self = [self initWithFrame:CGRectZero];
+
+ if(self)
+ {
+ _containingBar = popupBar;
+ }
+
+ return self;
+}
+
+- (void)_commonInit
+{
+ super.contentMode = UIViewContentModeScaleAspectFit;
+ self.clipsToBounds = NO;
+
+ UIView* imageContentsLayerView = [[UIView alloc] initWithFrame:self.bounds];
+ imageContentsLayerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ [self addSubview:imageContentsLayerView];
+ [(_LNPopupBarShadowedImageViewLayer*)self.layer setImageContentsLayer:imageContentsLayerView.layer];
+}
+
+- (void)setShadow:(NSShadow *)shadow
+{
+ _shadow = shadow;
+
+ self.layer.shadowOffset = _shadow.shadowOffset;
+ self.layer.shadowRadius = _shadow.shadowBlurRadius;
+
+ [self _updateShadowColor];
+ [self setNeedsLayout];
+}
+
+- (void)_updateShadowColor
+{
+ if([_shadow.shadowColor isKindOfClass:UIColor.class])
+ {
+ self.layer.shadowColor = [_shadow.shadowColor CGColor];
+ }
+ else
+ {
+ self.layer.shadowColor = (__bridge CGColorRef)_shadow.shadowColor;
+ }
+ self.layer.shadowOpacity = _shadow != nil ? 1.0 : 0.0;
+}
+
+- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
+{
+ [super traitCollectionDidChange:previousTraitCollection];
+
+ self.layer.rasterizationScale = self.traitCollection.displayScale;
+ [self _updateShadowColor];
+}
+
+- (void)layoutSubviews
+{
+ [super layoutSubviews];
+}
+
+- (void)setCornerRadius:(CGFloat)cornerRadius
+{
+ _cornerRadius = cornerRadius;
+ [(_LNPopupBarShadowedImageViewLayer*)self.layer setSuperCornerRadius:cornerRadius];
+}
+
+- (void)setContentMode:(UIViewContentMode)contentMode
+{
+ if(self.contentMode != contentMode)
+ {
+ [super setContentMode:contentMode];
+
+ [_containingBar setNeedsLayout];
+ }
+}
+
+- (void)setImage:(UIImage *)image
+{
+ if(self.image != image && [self.image isEqual:image] == NO)
+ {
+ [super setImage:image];
+
+ [_containingBar setNeedsLayout];
+ }
+}
+
+- (void)didMoveToWindow
+{
+ if(self.window == nil || _containingBar != nil)
+ {
+ return;
+ }
+
+ static NSString* vCFA = LNPopupHiddenString("_viewControllerForAncestor");
+
+ UIViewController* candidate = [self valueForKey:vCFA];
+ candidate.ln_discoveredTransitionView = self;
+}
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"%@ cornerRadius: %@ shadow: %@", super.description, @(self.cornerRadius), self.shadow];
+}
+
+@end
+
+@implementation LNPopupImageView (TransitionSupport) @end
diff --git a/LNPopupController/LNPopupController/Private/LNPopupInteractionPanGestureRecognizer.h b/LNPopupController/LNPopupController/Private/LNPopupInteractionPanGestureRecognizer.h
index a8cd4fa..34bc6e6 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupInteractionPanGestureRecognizer.h
+++ b/LNPopupController/LNPopupController/Private/LNPopupInteractionPanGestureRecognizer.h
@@ -2,8 +2,8 @@
// LNPopupInteractionPanGestureRecognizer.h
// LNPopupController
//
-// Created by Leo Natan on 15/07/2017.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2017-07-15.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/LNPopupInteractionPanGestureRecognizer.m b/LNPopupController/LNPopupController/Private/LNPopupInteractionPanGestureRecognizer.m
index be79266..13308ce 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupInteractionPanGestureRecognizer.m
+++ b/LNPopupController/LNPopupController/Private/LNPopupInteractionPanGestureRecognizer.m
@@ -2,14 +2,15 @@
// LNPopupInteractionPanGestureRecognizer.m
// LNPopupController
//
-// Created by Leo Natan on 15/07/2017.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2017-07-15.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNPopupInteractionPanGestureRecognizer.h"
#import "LNForwardingDelegate.h"
#import "UIViewController+LNPopupSupportPrivate.h"
#import "LNPopupController.h"
+#import "UIView+LNPopupSupportPrivate.h"
extern LNPopupInteractionStyle _LNPopupResolveInteractionStyleFromInteractionStyle(LNPopupInteractionStyle style);
@@ -64,6 +65,11 @@ extern LNPopupInteractionStyle _LNPopupResolveInteractionStyleFromInteractionSty
return rv;
}
+//- (BOOL)_panGestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer shouldTryToBeginHorizontallyWithEvent:(UIEvent*)event
+//{
+// return NO;
+//}
+
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if([NSStringFromClass(otherGestureRecognizer.view.class) containsString:@"DropShadow"])
@@ -82,6 +88,14 @@ extern LNPopupInteractionStyle _LNPopupResolveInteractionStyleFromInteractionSty
return YES;
}
+ //View hierarchy might add more and more views with gesture recognizers. Let's try to "import" them for our system.
+ [_popupController _fixupGestureRecognizer:otherGestureRecognizer];
+
+ if([otherGestureRecognizer.view isKindOfClass:UIScrollView.class] && [(UIScrollView*)otherGestureRecognizer.view _ln_hasVerticalContent] == NO && [(UIScrollView*)otherGestureRecognizer.view _ln_hasHorizontalContent] == NO)
+ {
+ return YES;
+ }
+
if(_popupController.popupControllerInternalState != LNPopupPresentationStateOpen)
{
if([self.forwardedDelegate respondsToSelector:_cmd])
@@ -111,7 +125,7 @@ extern LNPopupInteractionStyle _LNPopupResolveInteractionStyleFromInteractionSty
}
else
{
- return YES;
+ return [(UIScrollView*)otherGestureRecognizer.view _ln_hasVerticalContent] == YES;
}
}
@@ -139,6 +153,11 @@ extern LNPopupInteractionStyle _LNPopupResolveInteractionStyleFromInteractionSty
{
return NO;
}
+
+ if([otherGestureRecognizer.view isKindOfClass:UIScrollView.class] && [(UIScrollView*)otherGestureRecognizer.view _ln_hasVerticalContent] == NO)
+ {
+ return NO;
+ }
if([NSStringFromClass(otherGestureRecognizer.view.class) containsString:@"SwiftUI"])
{
@@ -170,6 +189,11 @@ extern LNPopupInteractionStyle _LNPopupResolveInteractionStyleFromInteractionSty
- (id)delegate
{
+ if([LNForwardingDelegate isCallerUIKit:NSThread.callStackReturnAddresses])
+ {
+ return _actualDelegate;
+ }
+
return _actualDelegate.forwardedDelegate;
}
diff --git a/LNPopupController/LNPopupController/Private/LNPopupItem+Private.h b/LNPopupController/LNPopupController/Private/LNPopupItem+Private.h
index 63ec4c0..36b6fab 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupItem+Private.h
+++ b/LNPopupController/LNPopupController/Private/LNPopupItem+Private.h
@@ -2,8 +2,8 @@
// LNPopupItem+Private.h
// LNPopupController
//
-// Created by Leo Natan on 7/25/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/LNPopupItem.m b/LNPopupController/LNPopupController/Private/LNPopupItem.m
index 92ddcd9..936f72e 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupItem.m
+++ b/LNPopupController/LNPopupController/Private/LNPopupItem.m
@@ -2,8 +2,8 @@
// LNPopupItem.m
// LNPopupController
//
-// Created by Leo Natan on 7/25/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNPopupItem+Private.h"
diff --git a/LNPopupController/LNPopupController/Private/LNPopupLongPressGestureRecognizer.h b/LNPopupController/LNPopupController/Private/LNPopupLongPressGestureRecognizer.h
index da0e13b..09ae4b5 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupLongPressGestureRecognizer.h
+++ b/LNPopupController/LNPopupController/Private/LNPopupLongPressGestureRecognizer.h
@@ -2,8 +2,8 @@
// LNPopupLongPressGestureRecognizer.h
// LNPopupController
//
-// Created by Leo Natan on 15/07/2017.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2017-07-15.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/LNPopupLongPressGestureRecognizer.m b/LNPopupController/LNPopupController/Private/LNPopupLongPressGestureRecognizer.m
index e019cce..812e4b3 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupLongPressGestureRecognizer.m
+++ b/LNPopupController/LNPopupController/Private/LNPopupLongPressGestureRecognizer.m
@@ -2,8 +2,8 @@
// LNPopupLongPressGestureRecognizer.m
// LNPopupController
//
-// Created by Leo Natan on 15/07/2017.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2017-07-15.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNPopupLongPressGestureRecognizer.h"
@@ -17,7 +17,7 @@
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
#if ! TARGET_OS_MACCATALYST
- if (@available(iOS 13.4, *))
+ if(@available(iOS 13.4, *))
{
if(touch.type == UITouchTypeIndirectPointer)
{
diff --git a/LNPopupController/LNPopupController/Private/LNPopupOpenTapGestureRecognizer.h b/LNPopupController/LNPopupController/Private/LNPopupOpenTapGestureRecognizer.h
index 6bbb97a..dac8d34 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupOpenTapGestureRecognizer.h
+++ b/LNPopupController/LNPopupController/Private/LNPopupOpenTapGestureRecognizer.h
@@ -2,8 +2,8 @@
// LNPopupOpenTapGestureRecognizer.h
// LNPopupController
//
-// Created by Leo Natan on 15/07/2017.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2017-07-15.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/LNPopupOpenTapGestureRecognizer.m b/LNPopupController/LNPopupController/Private/LNPopupOpenTapGestureRecognizer.m
index 707afbd..3e5e990 100644
--- a/LNPopupController/LNPopupController/Private/LNPopupOpenTapGestureRecognizer.m
+++ b/LNPopupController/LNPopupController/Private/LNPopupOpenTapGestureRecognizer.m
@@ -2,8 +2,8 @@
// LNPopupOpenTapGestureRecognizer.m
// LNPopupController
//
-// Created by Leo Natan on 15/07/2017.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2017-07-15.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "LNPopupOpenTapGestureRecognizer.h"
diff --git a/LNPopupController/LNPopupController/Private/MarqueeLabel.h b/LNPopupController/LNPopupController/Private/MarqueeLabel.h
index a4f4091..e48de36 100755
--- a/LNPopupController/LNPopupController/Private/MarqueeLabel.h
+++ b/LNPopupController/LNPopupController/Private/MarqueeLabel.h
@@ -6,15 +6,15 @@
// Copyright (c) 2011-2015 Charles Powell. All rights reserved.
//
-#define MarqueeLabel __MarqueeLabel
-#define MarqueeType __MarqueeType
-#define MLLeftRight __MLLeftRight
-#define MLRightLeft __MLRightLeft
-#define MLContinuous __MLContinuous
-#define MLContinuousReverse __MLContinuousReverse
-#define MLLeft __MLLeft
-#define MLRight __MLRight
-#define GradientSetupAnimation __GradientSetupAnimation
+#define MarqueeLabel LNMarqueeLabel
+#define MarqueeType LNMarqueeType
+#define MLLeftRight LNMLLeftRight
+#define MLRightLeft LNMLRightLeft
+#define MLContinuous LNMLContinuous
+#define MLContinuousReverse LNMLContinuousReverse
+#define MLLeft LNMLLeft
+#define MLRight LNMLRight
+#define GradientSetupAnimation LNGradientSetupAnimation
#import
diff --git a/LNPopupController/LNPopupController/Private/MarqueeLabel.m b/LNPopupController/LNPopupController/Private/MarqueeLabel.m
index e8f6da5..20f97e7 100755
--- a/LNPopupController/LNPopupController/Private/MarqueeLabel.m
+++ b/LNPopupController/LNPopupController/Private/MarqueeLabel.m
@@ -39,6 +39,9 @@ typedef void(^MLAnimationCompletionBlock)(BOOL finished);
@end
@interface MarqueeLabel()
+{
+ MarqueeType _userMarqueeType;
+}
@property (nonatomic, strong) UILabel *subLabel;
@@ -279,6 +282,8 @@ CGPoint MLOffsetCGPoint(CGPoint point, CGFloat offset);
- (void)layoutSubviews
{
[super layoutSubviews];
+
+ [self _updateEffectiveMarqueeType];
[self updateSublabel];
}
@@ -356,10 +361,6 @@ CGPoint MLOffsetCGPoint(CGPoint point, CGFloat offset);
return;
}
- // Label DOES need to scroll
-
- [self.subLabel setLineBreakMode:NSLineBreakByClipping];
-
// Spacing between primary and second sublabel must be at least equal to leadingBuffer, and at least equal to the fadeLength
CGFloat minTrailing = MAX(MAX(self.leadingBuffer, self.trailingBuffer), self.fadeLength);
@@ -968,7 +969,7 @@ CGPoint MLOffsetCGPoint(CGPoint point, CGFloat offset);
// Create new animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:property];
- if (@available(iOS 15.0, *))
+ if(@available(iOS 15.0, *))
{
CGFloat max = UIScreen.mainScreen.maximumFramesPerSecond;
animation.preferredFrameRateRange = CAFrameRateRangeMake(max, max, max);
@@ -1467,12 +1468,28 @@ CGPoint MLOffsetCGPoint(CGPoint point, CGFloat offset);
}
}
+- (void)_updateEffectiveMarqueeType
+{
+ BOOL isRTL = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft;
+
+ if(isRTL)
+ {
+ _marqueeType = _userMarqueeType == MLContinuous ? MLContinuousReverse : MLContinuous;
+ }
+ else
+ {
+ _marqueeType = _userMarqueeType == MLContinuous ? MLContinuous : MLContinuousReverse;
+ }
+}
+
- (void)setMarqueeType:(MarqueeType)marqueeType {
- if (marqueeType == _marqueeType) {
+ if (marqueeType == _userMarqueeType) {
return;
}
- _marqueeType = marqueeType;
+ _userMarqueeType = marqueeType;
+
+ [self _updateEffectiveMarqueeType];
[self updateSublabel];
}
diff --git a/LNPopupController/LNPopupController/Private/NSAttributedString+LNPopupSupport.h b/LNPopupController/LNPopupController/Private/NSAttributedString+LNPopupSupport.h
index f463f84..865cb7b 100644
--- a/LNPopupController/LNPopupController/Private/NSAttributedString+LNPopupSupport.h
+++ b/LNPopupController/LNPopupController/Private/NSAttributedString+LNPopupSupport.h
@@ -2,8 +2,8 @@
// NSAttributedString+LNPopupSupport.h
// LNPopupController
//
-// Created by Leo Natan on 9/19/21.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-09-19.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/NSAttributedString+LNPopupSupport.m b/LNPopupController/LNPopupController/Private/NSAttributedString+LNPopupSupport.m
index 924a389..2cecda8 100644
--- a/LNPopupController/LNPopupController/Private/NSAttributedString+LNPopupSupport.m
+++ b/LNPopupController/LNPopupController/Private/NSAttributedString+LNPopupSupport.m
@@ -2,8 +2,8 @@
// NSAttributedString+LNPopupSupport.m
// LNPopupController
//
-// Created by Leo Natan on 9/19/21.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-09-19.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "NSAttributedString+LNPopupSupport.h"
diff --git a/LNPopupController/LNPopupController/Private/UIContextMenuInteraction+LNPopupSupportPrivate.h b/LNPopupController/LNPopupController/Private/UIContextMenuInteraction+LNPopupSupportPrivate.h
index 22f8867..6e4c9f1 100644
--- a/LNPopupController/LNPopupController/Private/UIContextMenuInteraction+LNPopupSupportPrivate.h
+++ b/LNPopupController/LNPopupController/Private/UIContextMenuInteraction+LNPopupSupportPrivate.h
@@ -2,8 +2,8 @@
// UIContextMenuInteraction+LNPopupSupportPrivate.h
// LNPopupController
//
-// Created by Leo Natan on 3/28/21.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-03-28.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/UIContextMenuInteraction+LNPopupSupportPrivate.m b/LNPopupController/LNPopupController/Private/UIContextMenuInteraction+LNPopupSupportPrivate.mm
similarity index 79%
rename from LNPopupController/LNPopupController/Private/UIContextMenuInteraction+LNPopupSupportPrivate.m
rename to LNPopupController/LNPopupController/Private/UIContextMenuInteraction+LNPopupSupportPrivate.mm
index 42dcf17..561d3f8 100644
--- a/LNPopupController/LNPopupController/Private/UIContextMenuInteraction+LNPopupSupportPrivate.m
+++ b/LNPopupController/LNPopupController/Private/UIContextMenuInteraction+LNPopupSupportPrivate.mm
@@ -2,22 +2,17 @@
// UIContextMenuInteraction+LNPopupSupportPrivate.m
// LNPopupController
//
-// Created by Leo Natan on 3/28/21.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-03-28.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "UIViewController+LNPopupSupportPrivate.h"
#import "UIContextMenuInteraction+LNPopupSupportPrivate.h"
#import "LNPopupBar+Private.h"
#import "_LNPopupSwizzlingUtils.h"
+#import "_LNPopupBase64Utils.hh"
#ifndef LNPopupControllerEnforceStrictClean
-//_delegate_previewForHighlightingForConfiguration:
-static NSString* const dPFHFCBase64 = @"X2RlbGVnYXRlX3ByZXZpZXdGb3JIaWdobGlnaHRpbmdGb3JDb25maWd1cmF0aW9uOg==";
-//_delegate_contextMenuInteractionWillEndForConfiguration:presentation:
-static NSString* const dCMIWEFCpBase64 = @"X2RlbGVnYXRlX2NvbnRleHRNZW51SW50ZXJhY3Rpb25XaWxsRW5kRm9yQ29uZmlndXJhdGlvbjpwcmVzZW50YXRpb246";
-//_delegate_contextMenuInteractionWillDisplayForConfiguration:
-static NSString* const dCMIWDFCBase64 = @"X2RlbGVnYXRlX2NvbnRleHRNZW51SW50ZXJhY3Rpb25XaWxsRGlzcGxheUZvckNvbmZpZ3VyYXRpb246";
@implementation UIContextMenuInteraction (LNPopupSupportPrivate)
@@ -25,20 +20,17 @@ static NSString* const dCMIWDFCBase64 = @"X2RlbGVnYXRlX2NvbnRleHRNZW51SW50ZXJhY3
{
@autoreleasepool
{
- //_delegate_previewForHighlightingForConfiguration:
- NSString* selName = _LNPopupDecodeBase64String(dPFHFCBase64);
+ NSString* selName = LNPopupHiddenString("_delegate_previewForHighlightingForConfiguration:");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_ln_d_pFHFC:));
- //_delegate_contextMenuInteractionWillEndForConfiguration:presentation:
- selName = _LNPopupDecodeBase64String(dCMIWEFCpBase64);
+ selName = LNPopupHiddenString("_delegate_contextMenuInteractionWillEndForConfiguration:presentation:");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_ln_d_cMIWEFC:p:));
- //_delegate_contextMenuInteractionWillDisplayForConfiguration:
- selName = _LNPopupDecodeBase64String(dCMIWDFCBase64);
+ selName = LNPopupHiddenString("_delegate_contextMenuInteractionWillDisplayForConfiguration:");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_ln_d_cMIWDFC:));
diff --git a/LNPopupController/LNPopupController/Private/UIView+LNPopupSupportPrivate.h b/LNPopupController/LNPopupController/Private/UIView+LNPopupSupportPrivate.h
index 1601e0a..8a12704 100644
--- a/LNPopupController/LNPopupController/Private/UIView+LNPopupSupportPrivate.h
+++ b/LNPopupController/LNPopupController/Private/UIView+LNPopupSupportPrivate.h
@@ -2,8 +2,8 @@
// UIView+LNPopupSupportPrivate.h
// LNPopupController
//
-// Created by Leo Natan on 8/1/20.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2020-08-01.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
@@ -21,24 +21,32 @@ typedef void (^LNInWindowBlock)(dispatch_block_t);
@end
+UIEdgeInsets _LNEdgeInsetsFromDirectionalEdgeInsets(UIView* forView, NSDirectionalEdgeInsets edgeInsets);
+
@interface UIView (LNPopupSupportPrivate)
-- (void)_ln_triggerBarAppearanceRefreshIfNeededTriggeringLayout:(BOOL)layout;
-- (BOOL)_ln_scrollEdgeAppearanceRequiresFadeForPopupBar:(LNPopupBar*)popupBar;
+- (void)_ln_triggerBarAppearanceRefreshIfNeededTriggeringLayout:(BOOL)layout API_AVAILABLE(ios(13.0));
+- (BOOL)_ln_scrollEdgeAppearanceRequiresFadeForPopupBar:(LNPopupBar*)popupBar API_AVAILABLE(ios(13.0));
- (void)_ln_letMeKnowWhenViewInWindowHierarchy:(LNInWindowBlock)block;
- (void)_ln_forgetAboutIt;
- (nullable NSString*)_ln_effectGroupingIdentifierIfAvailable;
+- (void)_ln_freezeInsets;
+
@end
@interface UIView ()
-- (id)_lnpopup_scrollEdgeAppearance;
+- (id)_lnpopup_scrollEdgeAppearance API_AVAILABLE(ios(13.0));
@end
-#if TARGET_OS_MACCATALYST
+@interface UITabBar ()
+
+@property (nonatomic, getter=_ignoringLayoutDuringTransition, setter=_setIgnoringLayoutDuringTransition:) BOOL ignoringLayoutDuringTransition;
+
+@end
@interface UIWindow (MacCatalystSupport)
@@ -46,6 +54,15 @@ typedef void (^LNInWindowBlock)(dispatch_block_t);
@end
-#endif
-
NS_ASSUME_NONNULL_END
+
+@interface UIScrollView (LNPopupSupportPrivate)
+
+- (BOOL)_ln_hasHorizontalContent;
+- (BOOL)_ln_hasVerticalContent;
+- (BOOL)_ln_scrollingOnlyVertically;
+- (BOOL)_ln_isAtTop;
+
+@end
+
+@interface _LNPopupBarBackgroundGroupNameOverride: NSObject @end
diff --git a/LNPopupController/LNPopupController/Private/UIView+LNPopupSupportPrivate.m b/LNPopupController/LNPopupController/Private/UIView+LNPopupSupportPrivate.mm
similarity index 63%
rename from LNPopupController/LNPopupController/Private/UIView+LNPopupSupportPrivate.m
rename to LNPopupController/LNPopupController/Private/UIView+LNPopupSupportPrivate.mm
index 7858891..ba1adf6 100644
--- a/LNPopupController/LNPopupController/Private/UIView+LNPopupSupportPrivate.m
+++ b/LNPopupController/LNPopupController/Private/UIView+LNPopupSupportPrivate.mm
@@ -2,21 +2,28 @@
// UIView+LNPopupSupportPrivate.m
// LNPopupController
//
-// Created by Leo Natan on 8/1/20.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2020-08-01.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "UIView+LNPopupSupportPrivate.h"
#import "UIViewController+LNPopupSupportPrivate.h"
#import "LNPopupController.h"
#import "_LNPopupSwizzlingUtils.h"
+#import "_LNPopupBase64Utils.hh"
#import "LNPopupBar+Private.h"
#import "_LNPopupUIBarAppearanceProxy.h"
#import "_LNWeakRef.h"
-@import ObjectiveC;
-#if TARGET_OS_MACCATALYST
-@import AppKit;
-#endif
+#import
+
+@implementation _LNPopupBarBackgroundGroupNameOverride
+
++ (__kindof id)defaultValue
+{
+ return nil;
+}
+
+@end
static const void* LNPopupAttachedPopupController = &LNPopupAttachedPopupController;
static const void* LNPopupAwaitingViewInWindowHierarchyKey = &LNPopupAwaitingViewInWindowHierarchyKey;
@@ -24,39 +31,21 @@ static const void* LNPopupNotifyingKey = &LNPopupNotifyingKey;
static const void* LNPopupTabBarProgressKey = &LNPopupTabBarProgressKey;
static const void* LNPopupBarBackgroundViewForceAnimatedKey = &LNPopupBarBackgroundViewForceAnimatedKey;
-#if ! LNPopupControllerEnforceStrictClean
-//backdropGroupName
-static NSString* _bGN = @"YmFja2Ryb3BHcm91cE5hbWU=";
-//_UINavigationBarVisualProvider
-static NSString* _UINBVP = @"X1VJTmF2aWdhdGlvbkJhclZpc3VhbFByb3ZpZGVy";
-//_UINavigationBarVisualProviderLegacyIOS
-static NSString* _UINBVPLI = @"X1VJTmF2aWdhdGlvbkJhclZpc3VhbFByb3ZpZGVyTGVnYWN5SU9T";
-//_UINavigationBarVisualProviderModernIOS
-static NSString* _UINBVPMI = @"X1VJTmF2aWdhdGlvbkJhclZpc3VhbFByb3ZpZGVyTW9kZXJuSU9T";
-//updateBackgroundGroupName
-static NSString* _uBGN = @"dXBkYXRlQmFja2dyb3VuZEdyb3VwTmFtZQ==";
-//_viewControllerForAncestor
-static NSString* _vCFA = @"X3ZpZXdDb250cm9sbGVyRm9yQW5jZXN0b3I=";
-//_didMoveFromWindow:toWindow:
-static NSString* _dMFWtW = @"X2RpZE1vdmVGcm9tV2luZG93OnRvV2luZG93Og==";
-//_backdropViewLayerGroupName
-static NSString* _bVLGN = @"X2JhY2tkcm9wVmlld0xheWVyR3JvdXBOYW1l";
-//hostWindow
-static NSString* _hW = @"aG9zdFdpbmRvdw==";
-//attachedWindow
-static NSString* _aW = @"YXR0YWNoZWRXaW5kb3c=";
-//currentEvent
-static NSString* _cE = @"Y3VycmVudEV2ZW50";
-//backgroundTransitionProgress
-static NSString* _bTP = @"YmFja2dyb3VuZFRyYW5zaXRpb25Qcm9ncmVzcw==";
-//_UIBarBackground
-static NSString* _UBB = @"X1VJQmFyQmFja2dyb3VuZA==";
-//transitionBackgroundViewsAnimated:
-static NSString* _tBVA = @"dHJhbnNpdGlvbkJhY2tncm91bmRWaWV3c0FuaW1hdGVkOg==";
-//_backgroundView
-static NSString* _bV = @"X2JhY2tncm91bmRWaWV3";
+@interface __LNPopupUIViewFrozenInsets : NSObject @end
+@implementation __LNPopupUIViewFrozenInsets
-#endif
++ (void)load
+{
+ @autoreleasepool
+ {
+ const char* encoding = method_getTypeEncoding(class_getInstanceMethod(UIView.class, @selector(needsUpdateConstraints)));
+ class_addMethod(self, NSSelectorFromString(LNPopupHiddenString("_safeAreaInsetsFrozen")), imp_implementationWithBlock(^ (id self, SEL _cmd) {
+ return YES;
+ }), encoding);
+ }
+}
+
+@end
@interface UIViewController ()
@@ -97,107 +86,57 @@ static NSString* _bV = @"X2JhY2tncm91bmRWaWV3";
+ (void)load
{
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
+ @autoreleasepool
+ {
#if ! LNPopupControllerEnforceStrictClean
- if(@available(iOS 13.0, *))
- {
- //updateBackgroundGroupName
- SEL updateBackgroundGroupNameSEL = NSSelectorFromString(_LNPopupDecodeBase64String(_uBGN));
-
- id (^trampoline)(void (*)(id, SEL)) = ^ id (void (*orig)(id, SEL)){
- return ^ (id _self) {
- orig(_self, updateBackgroundGroupNameSEL);
-
- static NSString* key = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- //backdropGroupName
- key = _LNPopupDecodeBase64String(_bGN);
- });
-
- NSString* groupName = [_self valueForKey:key];
- if([groupName hasSuffix:@"🤡"] == NO)
- {
- [_self setValue:[NSString stringWithFormat:@"%@🤡", groupName] forKey:key];
- }
- };
+ SEL updateBackgroundGroupNameSEL = NSSelectorFromString(LNPopupHiddenString("updateBackgroundGroupName"));
+
+ id (^trampoline)(void (*)(id, SEL)) = ^ id (void (*orig)(id, SEL)){
+ return ^ (id _self) {
+ orig(_self, updateBackgroundGroupNameSEL);
+
+ static NSString* groupNameKey = LNPopupHiddenString("groupName");
+ static NSString* backgroundViewKey = LNPopupHiddenString("backgroundView");
+
+ id backgroundView = [_self valueForKey:backgroundViewKey];
+
+ NSString* groupName = [backgroundView valueForKey:groupNameKey];
+ if([groupName hasSuffix:@"🤡"] == NO)
+ {
+ [backgroundView setValue:[NSString stringWithFormat:@"%@🤡", groupName] forKey:groupNameKey];
+ }
};
-
- {
- //_UINavigationBarVisualProvider
- Class cls = NSClassFromString(_LNPopupDecodeBase64String(_UINBVP));
- Method m = class_getInstanceMethod(cls, updateBackgroundGroupNameSEL);
- void (*orig)(id, SEL) = (void*)method_getImplementation(m);
- method_setImplementation(m, imp_implementationWithBlock(trampoline(orig)));
- }
-
- {
- //_UINavigationBarVisualProviderLegacyIOS
- Class cls = NSClassFromString(_LNPopupDecodeBase64String(_UINBVPLI));
- Method m = class_getInstanceMethod(cls, updateBackgroundGroupNameSEL);
- void (*orig)(id, SEL) = (void*)method_getImplementation(m);
- method_setImplementation(m, imp_implementationWithBlock(trampoline(orig)));
- }
-
- {
- //_UINavigationBarVisualProviderModernIOS
- Class cls = NSClassFromString(_LNPopupDecodeBase64String(_UINBVPMI));
- Method m = class_getInstanceMethod(cls, updateBackgroundGroupNameSEL);
- void (*orig)(id, SEL) = (void*)method_getImplementation(m);
- method_setImplementation(m, imp_implementationWithBlock(trampoline(orig)));
- }
- }
- else
+ };
+
{
- //updateBackgroundGroupName
- SEL updateBackgroundsSEL = NSSelectorFromString(@"_updateBackgrounds");
-
- id (^trampoline)(void (*)(id, SEL)) = ^ id (void (*orig)(id, SEL)){
- return ^ (id _self) {
- orig(_self, updateBackgroundsSEL);
-
- static NSString* key = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- //groupName
- key = @"backgroundView.groupName";
- });
-
- NSString* groupName = [_self valueForKeyPath:key];
- if([groupName hasSuffix:@"🤡"] == NO)
- {
- [_self setValue:[NSString stringWithFormat:@"%@🤡", groupName] forKeyPath:key];
- }
- };
- };
-
- {
- //_UINavigationBarVisualProvider
- Class cls = NSClassFromString(_LNPopupDecodeBase64String(_UINBVP));
- Method m = class_getInstanceMethod(cls, updateBackgroundsSEL);
- void (*orig)(id, SEL) = (void*)method_getImplementation(m);
- method_setImplementation(m, imp_implementationWithBlock(trampoline(orig)));
- }
-
- {
- //_UINavigationBarVisualProviderLegacyIOS
- Class cls = NSClassFromString(_LNPopupDecodeBase64String(_UINBVPLI));
- Method m = class_getInstanceMethod(cls, updateBackgroundsSEL);
- void (*orig)(id, SEL) = (void*)method_getImplementation(m);
- method_setImplementation(m, imp_implementationWithBlock(trampoline(orig)));
- }
-
- {
- //_UINavigationBarVisualProviderModernIOS
- Class cls = NSClassFromString(_LNPopupDecodeBase64String(_UINBVPMI));
- Method m = class_getInstanceMethod(cls, updateBackgroundsSEL);
- void (*orig)(id, SEL) = (void*)method_getImplementation(m);
- method_setImplementation(m, imp_implementationWithBlock(trampoline(orig)));
- }
+ Class cls = NSClassFromString(LNPopupHiddenString("_UINavigationBarVisualProvider"));
+ Method m = class_getInstanceMethod(cls, updateBackgroundGroupNameSEL);
+ void (*orig)(id, SEL) = reinterpret_cast(method_getImplementation(m));
+ method_setImplementation(m, imp_implementationWithBlock(trampoline(orig)));
}
- NSString* sel = _LNPopupDecodeBase64String(_dMFWtW);
+ {
+ Class cls = NSClassFromString(LNPopupHiddenString("_UINavigationBarVisualProviderLegacyIOS"));
+ Method m = class_getInstanceMethod(cls, updateBackgroundGroupNameSEL);
+ void (*orig)(id, SEL) = reinterpret_cast(method_getImplementation(m));
+ method_setImplementation(m, imp_implementationWithBlock(trampoline(orig)));
+ }
+
+ {
+ Class cls = NSClassFromString(LNPopupHiddenString("_UINavigationBarVisualProviderModernIOS"));
+ Method m = class_getInstanceMethod(cls, updateBackgroundGroupNameSEL);
+ void (*orig)(id, SEL) = reinterpret_cast(method_getImplementation(m));
+ method_setImplementation(m, imp_implementationWithBlock(trampoline(orig)));
+ }
+
+ {
+ Class cls = NSClassFromString(LNPopupHiddenString("_UITabBarVisualProviderLegacyIOS"));
+ Method m = class_getInstanceMethod(cls, updateBackgroundGroupNameSEL);
+ void (*orig)(id, SEL) = reinterpret_cast(method_getImplementation(m));
+ method_setImplementation(m, imp_implementationWithBlock(trampoline(orig)));
+ }
+
+ NSString* sel = LNPopupHiddenString("_didMoveFromWindow:toWindow:");
LNSwizzleMethod(self,
NSSelectorFromString(sel),
@selector(_ln__dMFW:tW:));
@@ -206,7 +145,7 @@ static NSString* _bV = @"X2JhY2tncm91bmRWaWV3";
@selector(didMoveToWindow),
@selector(_ln_didMoveToWindow));
#endif
- });
+ }
}
- (void)_ln_triggerBarAppearanceRefreshIfNeededTriggeringLayout:(BOOL)layout
@@ -242,7 +181,7 @@ static NSString* _bV = @"X2JhY2tncm91bmRWaWV3";
#endif
LNAlwaysInline
-static void _LNNotify(UIView* self, NSMutableArray* waiting)
+void _LNNotify(UIView* self, NSMutableArray* waiting)
{
if(waiting.count == 0)
{
@@ -307,11 +246,7 @@ static void _LNNotify(UIView* self, NSMutableArray* waiting)
- (NSString*)_ln_effectGroupingIdentifierIfAvailable
{
#if ! LNPopupControllerEnforceStrictClean
- static NSString* key = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- key = _LNPopupDecodeBase64String(_bVLGN);
- });
+ static NSString* key = LNPopupHiddenString("_backdropViewLayerGroupName");
if([self respondsToSelector:NSSelectorFromString(key)])
{
@@ -326,9 +261,63 @@ static void _LNNotify(UIView* self, NSMutableArray* waiting)
#endif
}
+- (void)_ln_freezeInsets
+{
+ LNDynamicallySubclass(self, __LNPopupUIViewFrozenInsets.class);
+}
+
@end
-#if TARGET_OS_MACCATALYST
+#if ! LNPopupControllerEnforceStrictClean
+@interface UIWindow (ScrollToTopFix) @end
+@implementation UIWindow (ScrollToTopFix)
+
++ (void)load
+{
+ @autoreleasepool
+ {
+ NSString* selName = LNPopupHiddenString("_registeredScrollToTopViews");
+ LNSwizzleMethod(self,
+ NSSelectorFromString(selName),
+ @selector(_ln_rSTTV));
+ }
+}
+
+//_registeredScrollToTopViews
+- (NSArray*)_ln_rSTTV
+{
+ NSArray* rv = [self _ln_rSTTV];
+ NSMutableArray* popupRV = [NSMutableArray new];
+
+ static NSString* vCFA = LNPopupHiddenString("_viewControllerForAncestor");
+
+ for(UIView* scrollToTopCandidate in rv)
+ {
+ UIViewController* vc = [scrollToTopCandidate valueForKey:vCFA];
+
+ if(vc == nil)
+ {
+ continue;
+ }
+
+ BOOL fromPopup = vc._isContainedInOpenPopupController;
+ if(fromPopup)
+ {
+ [popupRV addObject:scrollToTopCandidate];
+ }
+ }
+
+ if(popupRV.count > 0)
+ {
+ return popupRV;
+ }
+
+ return rv;
+}
+
+@end
+
+#endif
@implementation UIWindow (MacCatalystSupport)
@@ -338,18 +327,11 @@ static void _LNNotify(UIView* self, NSMutableArray* waiting)
return nil;
#else
//hostWindow
- static NSString* hW;
+ static NSString* hW = LNPopupHiddenString("hostWindow");
//attachedWindow
- static NSString* aW;
+ static NSString* aW = LNPopupHiddenString("attachedWindow");
//currentEvent
- static NSString* cE;
-
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- hW = _LNPopupDecodeBase64String(_hW);
- aW = _LNPopupDecodeBase64String(_aW);
- cE = _LNPopupDecodeBase64String(_cE);
- });
+ static NSString* cE = LNPopupHiddenString("currentEvent");
//Obtain the actual NSWindow object
id hostingWindow = [self valueForKey:hW];
@@ -365,9 +347,6 @@ static void _LNNotify(UIView* self, NSMutableArray* waiting)
@end
-
-#endif
-
LNAlwaysInline
BOOL _LNBottomBarIsInPopupPresentation(NSObject* self)
{
@@ -403,14 +382,11 @@ id _LNPopupReturnScrollEdgeAppearanceOrStandardAppearance(id self, SEL standardA
#pragma clang diagnostic pop
}
-static BOOL __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(id bottomBar, LNPopupBar* popupBar) API_AVAILABLE(ios(13.0))
+API_AVAILABLE(ios(13.0))
+static BOOL __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(id bottomBar, LNPopupBar* popupBar)
{
//backgroundTransitionProgress
- static NSString* bTP = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- bTP = _LNPopupDecodeBase64String(_bTP);
- });
+ static NSString* bTP = LNPopupHiddenString("backgroundTransitionProgress");
BOOL isAtScrollEdge = [[bottomBar valueForKey:bTP] doubleValue] > 0;
@@ -433,6 +409,7 @@ static BOOL __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(id bottomBar, LNPop
{
if(@available(iOS 15.0, *))
{
+ LNSwizzleMethod(self, @selector(layoutSubviews), @selector(_ln_layoutSubviews));
#if ! LNPopupControllerEnforceStrictClean
LNSwizzleMethod(self, @selector(standardAppearance), @selector(_lnpopup_standardAppearance));
LNSwizzleMethod(self, @selector(compactAppearance), @selector(_lnpopup_compactAppearance));
@@ -445,6 +422,13 @@ static BOOL __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(id bottomBar, LNPop
}
}
+- (void)_ln_layoutSubviews
+{
+ [self _ln_layoutSubviews];
+
+ [self._ln_attachedPopupController _configurePopupBarFromBottomBarModifyingGroupingIdentifier:NO];
+}
+
- (void)_ln_triggerBarAppearanceRefreshIfNeededTriggeringLayout:(BOOL)layout
{
if(@available(iOS 15.0, *))
@@ -459,7 +443,7 @@ static BOOL __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(id bottomBar, LNPop
}
}
-- (BOOL)_ln_scrollEdgeAppearanceRequiresFadeForPopupBar:(LNPopupBar*)popupBar API_AVAILABLE(ios(13.0))
+- (BOOL)_ln_scrollEdgeAppearanceRequiresFadeForPopupBar:(LNPopupBar*)popupBar
{
return __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(self, popupBar);
}
@@ -590,13 +574,26 @@ static BOOL __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(id bottomBar, LNPop
@end
+static const void* LNPopupIgnoringLayoutDuringTransition = &LNPopupIgnoringLayoutDuringTransition;
+
@interface UITabBar (ScrollEdgeSupport) @end
@implementation UITabBar (ScrollEdgeSupport)
+- (BOOL)_ignoringLayoutDuringTransition
+{
+ return [objc_getAssociatedObject(self, LNPopupIgnoringLayoutDuringTransition) boolValue];
+}
+
+- (void)_setIgnoringLayoutDuringTransition:(BOOL)ignoringLayoutDuringTransition
+{
+ objc_setAssociatedObject(self, LNPopupIgnoringLayoutDuringTransition, @(ignoringLayoutDuringTransition), OBJC_ASSOCIATION_RETAIN);
+}
+
+ (void)load
{
@autoreleasepool
{
+ LNSwizzleMethod(self, @selector(setFrame:), @selector(_ln_setFrame:));
LNSwizzleMethod(self, @selector(layoutSubviews), @selector(_ln_layoutSubviews));
LNSwizzleMethod(self, @selector(setSelectedItem:), @selector(_ln_setSelectedItem:));
@@ -612,10 +609,10 @@ static BOOL __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(id bottomBar, LNPop
#if ! LNPopupControllerEnforceStrictClean
if(@available(iOS 17.0, *))
{
- Class cls = NSClassFromString(_LNPopupDecodeBase64String(_UBB));
- SEL sel = NSSelectorFromString(_LNPopupDecodeBase64String(_tBVA));
+ Class cls = NSClassFromString(LNPopupHiddenString("_UIBarBackground"));
+ SEL sel = NSSelectorFromString(LNPopupHiddenString("transitionBackgroundViewsAnimated:"));
Method m = class_getInstanceMethod(cls, sel);
- void (*orig)(id, SEL, BOOL) = (void*)method_getImplementation(m);
+ void (*orig)(id, SEL, BOOL) = reinterpret_cast(method_getImplementation(m));
method_setImplementation(m, imp_implementationWithBlock(^(id _self, BOOL animated) {
if([objc_getAssociatedObject(_self, LNPopupBarBackgroundViewForceAnimatedKey) boolValue] == YES)
{
@@ -629,6 +626,14 @@ static BOOL __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(id bottomBar, LNPop
}
}
+- (void)_ln_setFrame:(CGRect)frame
+{
+ if(self._ignoringLayoutDuringTransition == NO)
+ {
+ [self _ln_setFrame:frame];
+ }
+}
+
- (void)_ln_layoutSubviews
{
[self _ln_layoutSubviews];
@@ -656,7 +661,9 @@ static BOOL __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(id bottomBar, LNPop
if(@available(iOS 15.0, *))
{
#if ! LNPopupControllerEnforceStrictClean
- backgroundView = [self valueForKey:_LNPopupDecodeBase64String(_bV)];
+ static NSString* backgroundViewKey = LNPopupHiddenString("_backgroundView");
+
+ backgroundView = [self valueForKey:backgroundViewKey];
if(backgroundView != nil)
{
objc_setAssociatedObject(backgroundView, LNPopupBarBackgroundViewForceAnimatedKey, @(YES), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
@@ -686,12 +693,12 @@ static BOOL __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(id bottomBar, LNPop
}
}
-- (BOOL)_ln_scrollEdgeAppearanceRequiresFadeForPopupBar:(LNPopupBar*)popupBar API_AVAILABLE(ios(13.0))
+- (BOOL)_ln_scrollEdgeAppearanceRequiresFadeForPopupBar:(LNPopupBar*)popupBar
{
return __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(self, popupBar);
}
-- (UITabBarAppearance *)_lnpopup_scrollEdgeAppearance API_AVAILABLE(ios(13.0))
+- (UITabBarAppearance *)_lnpopup_scrollEdgeAppearance
{
return _LNPopupReturnScrollEdgeAppearanceOrStandardAppearance(self, @selector(standardAppearance), @selector(_lnpopup_scrollEdgeAppearance));
}
@@ -723,3 +730,115 @@ static BOOL __ln_scrollEdgeAppearanceRequiresFadeForPopupBar(id bottomBar, LNPop
}
@end
+
+@implementation UIScrollView (LNPopupSupportPrivate)
+
+static NSString* __ln_queueingScrollViewClassPrefix = LNPopupHiddenString("Queu");
+
+- (CGRect)_ln_adjustedBounds
+{
+ if([NSStringFromClass(self.class) containsString:__ln_queueingScrollViewClassPrefix])
+ {
+ return self.bounds;
+ }
+ else
+ {
+ return UIEdgeInsetsInsetRect(self.bounds, self.adjustedContentInset);
+ }
+}
+
+- (BOOL)_ln_hasHorizontalContent
+{
+ BOOL rv = self.contentSize.width > self._ln_adjustedBounds.size.width;
+
+// NSLog(@"_ln_hasHorizontalContent: %@ contentSize: %@ adjustedBounds: %@", @(rv), @(self.contentSize), @(self._ln_adjustedBounds));
+
+ return rv;
+}
+
+- (BOOL)_ln_hasVerticalContent
+{
+ BOOL rv = self.contentSize.height > self._ln_adjustedBounds.size.height;
+
+// NSLog(@"_ln_hasVerticalContent: %@ contentSize: %@ adjustedBounds: %@ ajustedInsets: %@", @(rv), @(self.contentSize), @(self._ln_adjustedBounds), @(self.adjustedContentInset));
+
+ return rv;
+}
+
+- (BOOL)_ln_scrollingOnlyVertically
+{
+ return self._ln_hasHorizontalContent == NO || [self.panGestureRecognizer translationInView:self].x == 0;
+}
+
+- (BOOL)_ln_isAtTop
+{
+ if([NSStringFromClass(self.class) containsString:__ln_queueingScrollViewClassPrefix])
+ {
+ if(self._ln_hasVerticalContent)
+ {
+ static SEL viewBeforeViewSEL = NSSelectorFromString(LNPopupHiddenString("_viewBeforeView:"));
+ static id (*viewBeforeView)(id, SEL, id) = reinterpret_cast(method_getImplementation(class_getInstanceMethod(self.class, viewBeforeViewSEL)));
+ static SEL visibleViewSEL = NSSelectorFromString(LNPopupHiddenString("visibleView"));
+ static id (*visibleView)(id, SEL) = reinterpret_cast(method_getImplementation(class_getInstanceMethod(self.class, visibleViewSEL)));
+
+ id visible = visibleView(self, visibleViewSEL);
+ return visible == nil || viewBeforeView(self, viewBeforeViewSEL, visible) == nil;
+ }
+ else
+ {
+ return YES;
+ }
+ }
+
+ return self.contentOffset.y <= - (self.adjustedContentInset.top);
+}
+
+@end
+
+UIEdgeInsets _LNEdgeInsetsFromDirectionalEdgeInsets(UIView* view, NSDirectionalEdgeInsets edgeInsets)
+{
+ if(view.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight)
+ {
+ return UIEdgeInsetsMake(edgeInsets.top, edgeInsets.leading, edgeInsets.bottom, edgeInsets.trailing);
+ }
+ else
+ {
+ return UIEdgeInsetsMake(edgeInsets.top, edgeInsets.trailing, edgeInsets.bottom, edgeInsets.leading);
+ }
+}
+
+#if ! LNPopupControllerEnforceStrictClean
+
+@interface UIVisualEffectView (LNPopupSupportPrivate) @end
+@implementation UIVisualEffectView (LNPopupSupportPrivate)
+
++ (void)load
+{
+ @autoreleasepool
+ {
+ if(@available(iOS 17.0, *))
+ {
+ NSString* selName = LNPopupHiddenString("_setGroupName:");
+ LNSwizzleMethod(self,
+ NSSelectorFromString(selName),
+ @selector(_ln_sGN:));
+ }
+ }
+}
+
+//_setGroupName:
+- (void)_ln_sGN:(NSString*)name API_AVAILABLE(ios(17.0))
+{
+ NSString* override = [self.traitCollection objectForTrait:_LNPopupBarBackgroundGroupNameOverride.class];
+ if(override != nil)
+ {
+ [self _ln_sGN:override];
+ return;
+ }
+
+ [self _ln_sGN:name];
+}
+
+@end
+
+#endif
diff --git a/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupport.m b/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupport.mm
similarity index 68%
rename from LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupport.m
rename to LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupport.mm
index e79812b..b2049ee 100644
--- a/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupport.m
+++ b/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupport.mm
@@ -2,8 +2,8 @@
// UIViewController+LNPopupSupport.m
// LNPopupController
//
-// Created by Leo Natan on 7/24/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "UIViewController+LNPopupSupportPrivate.h"
@@ -11,8 +11,10 @@
#import "_LNWeakRef.h"
#import "UIView+LNPopupSupportPrivate.h"
#import "_LNPopupSwizzlingUtils.h"
+#import "_LNPopupBase64Utils.hh"
#import "LNMath.h"
-@import ObjectiveC;
+#import "LNPopupBar+Private.h"
+#import
static const void* _LNPopupItemKey = &_LNPopupItemKey;
static const void* _LNPopupControllerKey = &_LNPopupControllerKey;
@@ -25,31 +27,26 @@ static const void* _LNPopupShouldExtendUnderSafeAreaKey = &_LNPopupShouldExtendU
const double LNSnapPercentDefault = 0.32;
+extern "C" {
+extern LNPopupInteractionStyle _LNPopupResolveInteractionStyleFromInteractionStyle(LNPopupInteractionStyle style);
+}
+
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation UIViewController (LNPopupSupportPrivate)
-@dynamic ln_popupController, popupPresentationContainerViewController, popupContentViewController, bottomBarSupport;
+@dynamic ln_popupController, popupPresentationContainerViewController, popupContentViewController, bottomBarSupport, ln_discoveredTransitionView;
@end
#pragma clang diagnostic pop
-#if ! LNPopupControllerEnforceStrictClean
-//_existingPresentationControllerImmediate:effective:
-static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlckltbWVkaWF0ZTplZmZlY3RpdmU6";
-#endif
-
@implementation UIViewController (LNPopupSupport)
- (UIPresentationController*)nonMemoryLeakingPresentationController
{
#if ! LNPopupControllerEnforceStrictClean
- static NSString* sel = nil;
- static id (*nonLeakingPresentationController)(id, SEL, BOOL, BOOL) = (id(*)(id, SEL, BOOL, BOOL))objc_msgSend;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- sel = _LNPopupDecodeBase64String(ePCIEBase64);
- });
+ static NSString* sel = LNPopupHiddenString("_existingPresentationControllerImmediate:effective:");;
+ static id (*nonLeakingPresentationController)(id, SEL, BOOL, BOOL) = reinterpret_cast(objc_msgSend);
return nonLeakingPresentationController(self, NSSelectorFromString(sel), NO, NO);
#else
@@ -68,8 +65,15 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
if(self.view.window == nil)
{
+ __weak __typeof(self) weakSelf = self;
[self.view _ln_letMeKnowWhenViewInWindowHierarchy:^(dispatch_block_t completionBlockInWindow) {
- [self presentPopupBarWithContentViewController:controller openPopup:openPopup animated:NO completion:^{
+ __strong __typeof(weakSelf) strongSelf = weakSelf;
+ if(strongSelf == nil)
+ {
+ return;
+ }
+
+ [strongSelf presentPopupBarWithContentViewController:controller openPopup:openPopup animated:NO completion:^{
if(completionBlock) { completionBlock(); }
completionBlockInWindow();
}];
@@ -100,8 +104,15 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
{
if(self.view.window == nil)
{
+ __weak __typeof(self) weakSelf = self;
[self.view _ln_letMeKnowWhenViewInWindowHierarchy:^(dispatch_block_t completionBlockInWindow) {
- [self openPopupAnimated:NO completion:^{
+ __strong __typeof(weakSelf) strongSelf = weakSelf;
+ if(strongSelf == nil)
+ {
+ return;
+ }
+
+ [strongSelf openPopupAnimated:NO completion:^{
if(completionBlock) { completionBlock(); }
completionBlockInWindow();
}];
@@ -117,8 +128,15 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
{
if(self.view.window == nil)
{
+ __weak __typeof(self) weakSelf = self;
[self.view _ln_letMeKnowWhenViewInWindowHierarchy:^(dispatch_block_t completionBlockInWindow) {
- [self closePopupAnimated:NO completion:^{
+ __strong __typeof(weakSelf) strongSelf = weakSelf;
+ if(strongSelf == nil)
+ {
+ return;
+ }
+
+ [strongSelf closePopupAnimated:NO completion:^{
if(completionBlock) { completionBlock(); }
completionBlockInWindow();
}];
@@ -134,8 +152,15 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
{
if(self.view.window == nil)
{
+ __weak __typeof(self) weakSelf = self;
[self.view _ln_letMeKnowWhenViewInWindowHierarchy:^(dispatch_block_t completionBlockInWindow) {
- [self dismissPopupBarAnimated:NO completion:^{
+ __strong __typeof(weakSelf) strongSelf = weakSelf;
+ if(strongSelf == nil)
+ {
+ return;
+ }
+
+ [strongSelf dismissPopupBarAnimated:NO completion:^{
if(completionBlock) { completionBlock(); }
completionBlockInWindow();
}];
@@ -189,6 +214,16 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
return [self.parentViewController _isContainedInPopupController];
}
+- (BOOL)_isContainedInOpenPopupController
+{
+ if(self.popupPresentationContainerViewController != nil)
+ {
+ return self.popupPresentationContainerViewController._ln_popupController_nocreate.popupControllerPublicState == LNPopupPresentationStateOpen;
+ }
+
+ return [self.parentViewController _isContainedInOpenPopupController];
+}
+
- (BOOL)_isContainedInPopupControllerOrDeallocated
{
if(objc_getAssociatedObject(self, _LNPopupPresentationContainerViewControllerKey) != nil)
@@ -243,6 +278,16 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
return NO;
}
+- (nullable UIView*)viewForPopupTransitionFromPresentationState:(LNPopupPresentationState)fromState toPresentationState:(LNPopupPresentationState)toState
+{
+ return self._ln_discoveredTransitionView;
+}
+
+- (nullable UIView*)_ln_transitionViewForPopupTransitionFromPresentationState:(LNPopupPresentationState)fromState toPresentationState:(LNPopupPresentationState)toState view:(out id _Nonnull __strong * _Nonnull)outView
+{
+ return nil;
+}
+
- (void)viewWillMoveToPopupContainerContentView:(LNPopupContentView *)popupContentView
{
@@ -268,6 +313,11 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
return (LNPopupInteractionStyle)[objc_getAssociatedObject(self, _LNPopupInteractionStyleKey) unsignedIntegerValue];
}
+- (LNPopupInteractionStyle)effectivePopupInteractionStyle
+{
+ return _LNPopupResolveInteractionStyleFromInteractionStyle((LNPopupInteractionStyle)[objc_getAssociatedObject(self, _LNPopupInteractionStyleKey) unsignedIntegerValue]);
+}
+
- (void)setPopupInteractionStyle:(LNPopupInteractionStyle)popupInteractionStyle
{
objc_setAssociatedObject(self, _LNPopupInteractionStyleKey, @(popupInteractionStyle), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
@@ -310,6 +360,28 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
self._ln_popupController.wantsFeedbackGeneration = allowPopupHapticFeedbackGeneration;
}
+static const void* _LNPopupContentControllerDiscoveredTransitionView = &_LNPopupContentControllerDiscoveredTransitionView;
+
+- (void)_ln_setDiscoveredTransitionView:(LNPopupImageView *)ln_discoveredShadowedImageView
+{
+ id objToSet = nil;
+ if(ln_discoveredShadowedImageView != nil)
+ {
+ objToSet = [_LNWeakRef refWithObject:ln_discoveredShadowedImageView];
+ }
+ objc_setAssociatedObject(self, _LNPopupContentControllerDiscoveredTransitionView, objToSet, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+- (LNPopupImageView *)_ln_discoveredTransitionView
+{
+ _LNWeakRef* rv = objc_getAssociatedObject(self, _LNPopupContentControllerDiscoveredTransitionView);
+ if(rv != nil && rv.object == nil)
+ {
+ [self _ln_setDiscoveredTransitionView:nil];
+ }
+ return rv.object;
+}
+
@end
@implementation UIViewController (LNCustomContainerPopupSupport)
@@ -374,9 +446,37 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
return CGRectZero;
}
+- (CGFloat)_ln_popupOffsetForPopupBarStyle:(LNPopupBarStyle)barStyle
+{
+ if(barStyle != LNPopupBarStyleFloating /*|| UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone*/)
+ {
+ return 0.0;
+ }
+
+ id dockingView = self.bottomDockingViewForPopupBar;
+
+ if(dockingView != nil && ([dockingView isKindOfClass:UIToolbar.class] || [dockingView isKindOfClass:UITabBar.class]) == NO)
+ {
+ //User docking view, do not offset.
+ return 0.0;
+ }
+
+ if(LNPopupBar.isCatalystApp)
+ {
+ return -7.0;
+ }
+
+ if(self.view.window.safeAreaInsets.bottom == 0)
+ {
+ return -4.0;
+ }
+
+ return self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular ? 7.0 : 0.0;
+}
+
- (CGRect)defaultFrameForBottomDockingView_internal
{
- CGFloat safeAreaAddition = self.view.safeAreaInsets.bottom - _LNPopupSafeAreas(self).bottom;
+ CGFloat safeAreaAddition = self.view.safeAreaInsets.bottom - _LNPopupSafeAreaInsets(self).bottom;
if(self.presentingViewController != nil && [NSStringFromClass(self.nonMemoryLeakingPresentationController.class) containsString:@"Preview"])
{
@@ -386,9 +486,13 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
return CGRectMake(0, self.view.bounds.size.height - safeAreaAddition, self.view.bounds.size.width, safeAreaAddition);
}
-- (CGRect)defaultFrameForBottomDockingView_internalOrDeveloper
+- (CGRect)_defaultFrameForBottomDockingViewForPopupBar:(LNPopupBar*)popupBar
{
- return [self bottomDockingViewForPopupBar] != nil ? [self defaultFrameForBottomDockingView] : [self defaultFrameForBottomDockingView_internal];
+ LNPopupBarStyle barStyle = popupBar != nil ? popupBar.resolvedStyle : _LNPopupResolveBarStyleFromBarStyle(LNPopupBarStyleDefault);
+
+ CGRect rv = [self bottomDockingViewForPopupBar] != nil ? [self defaultFrameForBottomDockingView] : [self defaultFrameForBottomDockingView_internal];
+ rv.origin.y += [self _ln_popupOffsetForPopupBarStyle:barStyle];
+ return rv;
}
- (BOOL)shouldExtendPopupBarUnderSafeArea
@@ -428,6 +532,16 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
return [self.topViewController positionPopupCloseButton:popupCloseButton];
}
+- (nullable UIView*)viewForPopupTransitionFromPresentationState:(LNPopupPresentationState)fromState toPresentationState:(LNPopupPresentationState)toState
+{
+ return [self.topViewController viewForPopupTransitionFromPresentationState:fromState toPresentationState:toState];
+}
+
+- (nullable UIView*)_ln_transitionViewForPopupTransitionFromPresentationState:(LNPopupPresentationState)fromState toPresentationState:(LNPopupPresentationState)toState view:(out id _Nonnull __strong * _Nonnull)outView
+{
+ return [self.topViewController _ln_transitionViewForPopupTransitionFromPresentationState:fromState toPresentationState:toState view:outView];
+}
+
@end
@implementation UITabBarController (LNPopupSupport)
@@ -437,4 +551,14 @@ static NSString* const ePCIEBase64 = @"X2V4aXN0aW5nUHJlc2VudGF0aW9uQ29udHJvbGxlc
return [self.selectedViewController positionPopupCloseButton:popupCloseButton];
}
+- (nullable UIView*)viewForPopupTransitionFromPresentationState:(LNPopupPresentationState)fromState toPresentationState:(LNPopupPresentationState)toState
+{
+ return [self.selectedViewController viewForPopupTransitionFromPresentationState:fromState toPresentationState:toState];
+}
+
+- (nullable UIView*)_ln_transitionViewForPopupTransitionFromPresentationState:(LNPopupPresentationState)fromState toPresentationState:(LNPopupPresentationState)toState view:(out id _Nonnull __strong * _Nonnull)outView
+{
+ return [self.selectedViewController _ln_transitionViewForPopupTransitionFromPresentationState:fromState toPresentationState:toState view:outView];
+}
+
@end
diff --git a/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupportPrivate.h b/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupportPrivate.h
index ba58453..b123500 100644
--- a/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupportPrivate.h
+++ b/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupportPrivate.h
@@ -2,13 +2,15 @@
// UIViewController+LNPopupSupportPrivate.h
// LNPopupController
//
-// Created by Leo Natan on 7/25/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
#import "_LNPopupBarBackgroundView.h"
+CF_EXTERN_C_BEGIN
+
@class LNPopupController;
NS_ASSUME_NONNULL_BEGIN
@@ -26,13 +28,15 @@ static inline __attribute__((always_inline)) UIEdgeInsets __LNEdgeInsetsSum(UIEd
extern BOOL __ln_popup_suppressViewControllerLifecycle;
-UIEdgeInsets _LNPopupSafeAreas(id self);
+UIEdgeInsets _LNPopupSafeAreaInsets(id self);
void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller, BOOL layout, UIEdgeInsets popupEdgeInsets);
@interface _LNPopupBottomBarSupport : UIView @end
@interface UIViewController (LNPopupSupportPrivate)
+- (void)_ln_updateSafeAreaInsets;
+
- (BOOL)_ln_shouldDisplayBottomShadowViewDuringTransition;
- (BOOL)_ln_reallyShouldExtendPopupBarUnderSafeArea;
@@ -51,6 +55,7 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
- (nullable _LNPopupBottomBarSupport *)_ln_bottomBarSupport_nocreate;
- (BOOL)_isContainedInPopupController;
+- (BOOL)_isContainedInOpenPopupController;
- (BOOL)_isContainedInPopupControllerOrDeallocated;
- (BOOL)_ignoringLayoutDuringTransition;
@@ -58,20 +63,32 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
- (nullable UIView *)bottomDockingViewForPopup_nocreateOrDeveloper;
- (nonnull UIView *)bottomDockingViewForPopup_internalOrDeveloper;
+- (CGFloat)_ln_popupOffsetForPopupBarStyle:(LNPopupBarStyle)barStyle;
+
- (CGRect)defaultFrameForBottomDockingView_internal;
-- (CGRect)defaultFrameForBottomDockingView_internalOrDeveloper;
+- (CGRect)_defaultFrameForBottomDockingViewForPopupBar:(LNPopupBar*)LNPopupBar;
- (_LNPopupBarBackgroundView*)_ln_bottomBarExtension_nocreate;
- (_LNPopupBarBackgroundView*)_ln_bottomBarExtension;
- (void)_userFacing_viewWillAppear:(BOOL)animated;
-- (void)_userFacing_viewIsAppearing:(BOOL)animated API_AVAILABLE(ios(13.0));
+- (void)_userFacing_viewIsAppearing:(BOOL)animated;
- (void)_userFacing_viewDidAppear:(BOOL)animated;
- (void)_userFacing_viewWillDisappear:(BOOL)animated;
- (void)_userFacing_viewDidDisappear:(BOOL)animated;
+- (BOOL)_ln_isObjectFromSwiftUI;
+
+- (BOOL)_ln_shouldPopupContentAnyFadeForTransition;
+- (BOOL)_ln_shouldPopupContentViewFadeForTransition;
+@property (nullable, nonatomic, weak, setter=_ln_setDiscoveredTransitionView:, getter=_ln_discoveredTransitionView) LNPopupImageView* ln_discoveredTransitionView;
+
+- (nullable UIView*)_ln_transitionViewForPopupTransitionFromPresentationState:(LNPopupPresentationState)fromState toPresentationState:(LNPopupPresentationState)toState view:(out id _Nonnull __strong * _Nonnull)outView;
+
@end
@interface _LN_UIViewController_AppearanceControl : UIViewController @end
NS_ASSUME_NONNULL_END
+
+CF_EXTERN_C_END
diff --git a/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupportPrivate.m b/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupportPrivate.mm
similarity index 66%
rename from LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupportPrivate.m
rename to LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupportPrivate.mm
index aef720d..ea0ba20 100644
--- a/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupportPrivate.m
+++ b/LNPopupController/LNPopupController/Private/UIViewController+LNPopupSupportPrivate.mm
@@ -2,17 +2,18 @@
// UIViewController+LNPopupSupportPrivate.m
// LNPopupController
//
-// Created by Leo Natan on 7/25/15.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2015-08-23.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "UIViewController+LNPopupSupportPrivate.h"
#import "LNPopupController.h"
#import "_LNPopupSwizzlingUtils.h"
+#import "_LNPopupBase64Utils.hh"
#import "UIView+LNPopupSupportPrivate.h"
-@import ObjectiveC;
-@import Darwin;
+#import
+#import
static const void* LNToolbarHiddenBeforeTransition = &LNToolbarHiddenBeforeTransition;
static const void* LNPopupAdjustingInsets = &LNPopupAdjustingInsets;
@@ -38,6 +39,8 @@ BOOL __ln_popup_suppressViewControllerLifecycle = NO;
@interface _LNPopupBarExtensionView : _LNPopupBarBackgroundView @end
@implementation _LNPopupBarExtensionView
+#if DEBUG
+
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
@@ -48,6 +51,8 @@ BOOL __ln_popup_suppressViewControllerLifecycle = NO;
[super setAlpha:alpha];
}
+#endif
+
@end
@interface NSObject ()
@@ -57,41 +62,6 @@ BOOL __ln_popup_suppressViewControllerLifecycle = NO;
@end
#ifndef LNPopupControllerEnforceStrictClean
-//_hideBarWithTransition:isExplicit:duration:
-static NSString* const hBWTiEDBase64 = @"X2hpZGVCYXJXaXRoVHJhbnNpdGlvbjppc0V4cGxpY2l0OmR1cmF0aW9uOg==";
-//_showBarWithTransition:isExplicit:duration:
-static NSString* const sBWTiEDBase64 = @"X3Nob3dCYXJXaXRoVHJhbnNpdGlvbjppc0V4cGxpY2l0OmR1cmF0aW9uOg==";
-//_setToolbarHidden:edge:duration:
-static NSString* const sTHedBase64 = @"X3NldFRvb2xiYXJIaWRkZW46ZWRnZTpkdXJhdGlvbjo=";
-//_viewControllerUnderlapsStatusBar
-static NSString* const vCUSBBase64 = @"X3ZpZXdDb250cm9sbGVyVW5kZXJsYXBzU3RhdHVzQmFy";
-//_hideShowNavigationBarDidStop:finished:context:
-static NSString* const hSNBDSfcBase64 = @"X2hpZGVTaG93TmF2aWdhdGlvbkJhckRpZFN0b3A6ZmluaXNoZWQ6Y29udGV4dDo=";
-//_viewSafeAreaInsetsFromScene
-static NSString* const vSAIFSBase64 = @"X3ZpZXdTYWZlQXJlYUluc2V0c0Zyb21TY2VuZQ==";
-//_updateLayoutForStatusBarAndInterfaceOrientation
-static NSString* const uLFSBAIO = @"X3VwZGF0ZUxheW91dEZvclN0YXR1c0JhckFuZEludGVyZmFjZU9yaWVudGF0aW9u";
-//_updateContentOverlayInsetsFromParentIfNecessary
-static NSString* const uCOIFPIN = @"X3VwZGF0ZUNvbnRlbnRPdmVybGF5SW5zZXRzRnJvbVBhcmVudElmTmVjZXNzYXJ5";
-//_accessibilitySpeakThisViewController
-static NSString* const aSTVC = @"X2FjY2Vzc2liaWxpdHlTcGVha1RoaXNWaWV3Q29udHJvbGxlcg==";
-//setParentViewController:
-static NSString* const sPVC = @"c2V0UGFyZW50Vmlld0NvbnRyb2xsZXI6";
-//UIViewControllerAccessibility
-static NSString* const uiVCA = @"VUlWaWV3Q29udHJvbGxlckFjY2Vzc2liaWxpdHk=";
-//UINavigationControllerAccessibility
-static NSString* const uiNVCA = @"VUlOYXZpZ2F0aW9uQ29udHJvbGxlckFjY2Vzc2liaWxpdHk=";
-//UITabBarControllerAccessibility
-static NSString* const uiTBCA = @"VUlUYWJCYXJDb250cm9sbGVyQWNjZXNzaWJpbGl0eQ==";
-//_prepareTabBar
-static NSString* const pTBBase64 = @"X3ByZXBhcmVUYWJCYXI=";
-
-//_setContentOverlayInsets:andLeftMargin:rightMargin:
-static NSString* const sCOIaLMrM = @"X3NldENvbnRlbnRPdmVybGF5SW5zZXRzOmFuZExlZnRNYXJnaW46cmlnaHRNYXJnaW46";
-//_contentMargin
-static NSString* const cM = @"X2NvbnRlbnRNYXJnaW4=";
-//_setContentMargin:
-static NSString* const sCM = @"X3NldENvbnRlbnRNYXJnaW46";
//_accessibilitySpeakThisViewController
static UIViewController* (*__orig_uiVCA_aSTVC)(id, SEL);
@@ -100,6 +70,19 @@ static UIViewController* (*__orig_uiTBCA_aSTVC)(id, SEL);
#endif
+static NSTimeInterval __ln_durationForTransition(UIViewController* vc, NSUInteger transition)
+{
+#ifndef LNPopupControllerEnforceStrictClean
+ //durationForTransition:
+ static SEL dFT = NSSelectorFromString(LNPopupHiddenString("durationForTransition:"));
+ static NSTimeInterval (*specialized_objc_msgSend)(id, SEL, NSUInteger) = reinterpret_cast(objc_msgSend);
+
+ return specialized_objc_msgSend(vc, dFT, transition);
+#else
+ return 0.5;
+#endif
+}
+
/**
A helper view for view controllers without real bottom bars.
*/
@@ -132,25 +115,25 @@ static void __accessibilityBundleLoadHandler(void)
return;
}
- NSString* selName = _LNPopupDecodeBase64String(aSTVC);
+ NSString* selName = LNPopupHiddenString("_accessibilitySpeakThisViewController");
//UIViewControllerAccessibility
//_accessibilitySpeakThisViewController
- NSString* clsName = _LNPopupDecodeBase64String(uiVCA);
+ NSString* clsName = LNPopupHiddenString("UIViewControllerAccessibility");
Method m1 = class_getInstanceMethod(NSClassFromString(clsName), NSSelectorFromString(selName));
- __orig_uiVCA_aSTVC = (void*)method_getImplementation(m1);
+ __orig_uiVCA_aSTVC = reinterpret_cast(method_getImplementation(m1));
Method m2 = class_getInstanceMethod([UIViewController class], NSSelectorFromString(@"_aSTVC"));
method_exchangeImplementations(m1, m2);
- clsName = _LNPopupDecodeBase64String(uiNVCA);
+ clsName = LNPopupHiddenString("UINavigationControllerAccessibility");
m1 = class_getInstanceMethod(NSClassFromString(clsName), NSSelectorFromString(selName));
- __orig_uiNVCA_aSTVC = (void*)method_getImplementation(m1);
+ __orig_uiNVCA_aSTVC = reinterpret_cast(method_getImplementation(m1));
m2 = class_getInstanceMethod([UINavigationController class], NSSelectorFromString(@"_aSTVC"));
method_exchangeImplementations(m1, m2);
- clsName = _LNPopupDecodeBase64String(uiTBCA);
+ clsName = LNPopupHiddenString("UITabBarControllerAccessibility");
m1 = class_getInstanceMethod(NSClassFromString(clsName), NSSelectorFromString(selName));
- __orig_uiTBCA_aSTVC = (void*)method_getImplementation(m1);
+ __orig_uiTBCA_aSTVC = reinterpret_cast(method_getImplementation(m1));
m2 = class_getInstanceMethod([UITabBarController class], NSSelectorFromString(@"_aSTVC"));
method_exchangeImplementations(m1, m2);
@@ -162,6 +145,16 @@ static void __accessibilityBundleLoadHandler(void)
#pragma mark - UIViewController
+BOOL __ln_alreadyInHideShowBar = NO;
+UIRectEdge __ln_hideBarEdge = UIRectEdgeNone;
+
+#if __has_include()
+#define HAS_SWIFT_UI 1
+CF_EXTERN_C_BEGIN
+extern void __ln_doNotCall__fixUIHostingViewHitTest(void);
+CF_EXTERN_C_END
+#endif
+
@interface UIViewController (LNPopupLayout) @end
@implementation UIViewController (LNPopupLayout)
@@ -171,6 +164,13 @@ static void __accessibilityBundleLoadHandler(void)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
+#if HAS_SWIFT_UI
+ if(@available(iOS 13.0, *))
+ {
+ __ln_doNotCall__fixUIHostingViewHitTest();
+ }
+#endif
+
__LNPopupBuggyAdditionalSafeAreaClasses = [NSSet setWithObjects:UINavigationController.class, UITabBarController.class, nil];
if(@available(iOS 13.0, *))
@@ -216,6 +216,10 @@ static void __accessibilityBundleLoadHandler(void)
@selector(setNeedsStatusBarAppearanceUpdate),
@selector(_ln_setNeedsStatusBarAppearanceUpdate));
+ LNSwizzleMethod(self,
+ @selector(setNeedsUpdateOfHomeIndicatorAutoHidden),
+ @selector(_ln_setNeedsUpdateOfHomeIndicatorAutoHidden));
+
LNSwizzleMethod(self,
@selector(childViewControllerForStatusBarStyle),
@selector(_ln_childViewControllerForStatusBarStyle));
@@ -224,6 +228,10 @@ static void __accessibilityBundleLoadHandler(void)
@selector(childViewControllerForStatusBarHidden),
@selector(_ln_childViewControllerForStatusBarHidden));
+ LNSwizzleMethod(self,
+ @selector(childViewControllerForHomeIndicatorAutoHidden),
+ @selector(_ln_childViewControllerForHomeIndicatorAutoHidden));
+
LNSwizzleMethod(self,
@selector(viewWillTransitionToSize:withTransitionCoordinator:),
@selector(_ln_viewWillTransitionToSize:withTransitionCoordinator:));
@@ -237,22 +245,19 @@ static void __accessibilityBundleLoadHandler(void)
@selector(_ln_presentViewController:animated:completion:));
#ifndef LNPopupControllerEnforceStrictClean
- //_viewControllerUnderlapsStatusBar
- NSString* selName = _LNPopupDecodeBase64String(vCUSBBase64);
+ NSString* selName = LNPopupHiddenString("_viewControllerUnderlapsStatusBar");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_vCUSB));
- //_updateLayoutForStatusBarAndInterfaceOrientation
- selName = _LNPopupDecodeBase64String(uLFSBAIO);
+ selName = LNPopupHiddenString("_updateLayoutForStatusBarAndInterfaceOrientation");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_uLFSBAIO));
if(@available(iOS 15.0, *))
{
- //_updateContentOverlayInsetsFromParentIfNecessary
- selName = _LNPopupDecodeBase64String(uCOIFPIN);
+ selName = LNPopupHiddenString("_updateContentOverlayInsetsFromParentIfNecessary");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_uCOIFPIN));
@@ -261,15 +266,13 @@ static void __accessibilityBundleLoadHandler(void)
}
else
{
- //_viewSafeAreaInsetsFromScene
- selName = _LNPopupDecodeBase64String(vSAIFSBase64);
+ selName = LNPopupHiddenString("_viewSafeAreaInsetsFromScene");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_vSAIFS));
}
- //setParentViewController:
- selName = _LNPopupDecodeBase64String(sPVC);
+ selName = LNPopupHiddenString("setParentViewController:");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_ln_sPVC:));
@@ -278,6 +281,16 @@ static void __accessibilityBundleLoadHandler(void)
}
}
+- (void)_ln_updateSafeAreaInsets
+{
+#ifndef LNPopupControllerEnforceStrictClean
+ static SEL sel = NSSelectorFromString(LNPopupHiddenString("_updateContentOverlayInsetsForSelfAndChildren"));
+ static void(*objc_msgSend_uCOIFSAC)(id, SEL) = reinterpret_cast(objc_msgSend);
+
+ objc_msgSend_uCOIFSAC(self, sel);
+#endif
+}
+
- (BOOL)_ln_isModalInPresentation
{
if(self._ln_popupController_nocreate.popupControllerInternalState >= _LNPopupPresentationStateTransitioning)
@@ -288,13 +301,38 @@ static void __accessibilityBundleLoadHandler(void)
return [self _ln_isModalInPresentation];
}
-- (void)_ln_popup_setOverrideUserInterfaceStyle:(UIUserInterfaceStyle)overrideUserInterfaceStyle API_AVAILABLE(ios(13.0))
+- (BOOL)_ln_isObjectFromSwiftUI
+{
+ static NSString* key = LNPopupHiddenString("_isFromSwiftUI");
+ return [self.class respondsToSelector:NSSelectorFromString(key)] && [[self.class valueForKey:key] boolValue];
+}
+
+- (BOOL)_ln_shouldPopupContentAnyFadeForTransition
+{
+ BOOL bottomBarIsVisible = [self.bottomDockingViewForPopup_internalOrDeveloper isKindOfClass:_LNPopupBottomBarSupport.class] == NO && self.ln_popupController.bottomBar.hidden == NO && self.ln_popupController.bottomBar.window != nil;
+
+ return self.popupBar.window.safeAreaInsets.bottom != 0 || bottomBarIsVisible;
+}
+
+- (BOOL)_ln_shouldPopupContentViewFadeForTransition
+{
+ BOOL bottomBarExtensionIsVisible = self._ln_bottomBarExtension_nocreate != nil && self._ln_bottomBarExtension_nocreate.isHidden == NO && self._ln_bottomBarExtension_nocreate.alpha > 0 && self._ln_bottomBarExtension_nocreate.frame.size.height > 0;
+
+ BOOL bottomBarIsVisible = [self.bottomDockingViewForPopup_internalOrDeveloper isKindOfClass:_LNPopupBottomBarSupport.class] == NO && self.ln_popupController.bottomBar.hidden == NO && self.ln_popupController.bottomBar.window != nil;
+
+ return bottomBarExtensionIsVisible == NO && bottomBarIsVisible == NO;
+}
+
+- (void)_ln_popup_setOverrideUserInterfaceStyle:(UIUserInterfaceStyle)overrideUserInterfaceStyle
{
[self _ln_popup_setOverrideUserInterfaceStyle:overrideUserInterfaceStyle];
- if(self._isContainedInPopupController)
+ if(@available(iOS 13.0, *))
{
- [self.popupPresentationContainerViewController.popupContentView setControllerOverrideUserInterfaceStyle:overrideUserInterfaceStyle];
+ if(self._isContainedInPopupController)
+ {
+ [self.popupPresentationContainerViewController.popupContentView setControllerOverrideUserInterfaceStyle:overrideUserInterfaceStyle];
+ }
}
}
@@ -323,7 +361,7 @@ static inline __attribute__((always_inline)) void _LNSetPopupSafeAreaInsets(id s
{
objc_setAssociatedObject(self, LNPopupAdditionalSafeAreaInsets, [NSValue valueWithUIEdgeInsets:additionalSafeAreaInsets], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- UIEdgeInsets user = _LNUserSafeAreas(self);
+ UIEdgeInsets user = _LNUserSafeAreaInsets(self);
_LNUpdateUserSafeAreaInsets(self, user, additionalSafeAreaInsets);
}
@@ -332,7 +370,7 @@ static inline __attribute__((always_inline)) void _LNSetPopupSafeAreaInsets(id s
{
objc_setAssociatedObject(self, LNUserAdditionalSafeAreaInsets, [NSValue valueWithUIEdgeInsets:additionalSafeAreaInsets], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- UIEdgeInsets popup = _LNPopupSafeAreas(self);
+ UIEdgeInsets popup = _LNPopupSafeAreaInsets(self);
_LNUpdateUserSafeAreaInsets(self, additionalSafeAreaInsets, popup);
}
@@ -342,12 +380,12 @@ static inline __attribute__((always_inline)) void _LNSetPopupSafeAreaInsets(id s
objc_setAssociatedObject(self, LNPopupChildAdditiveSafeAreaInsets, [NSValue valueWithUIEdgeInsets:childAdditiveSafeAreaInsets], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-UIEdgeInsets _LNPopupSafeAreas(id self)
+UIEdgeInsets _LNPopupSafeAreaInsets(id self)
{
return [objc_getAssociatedObject(self, LNPopupAdditionalSafeAreaInsets) UIEdgeInsetsValue];
}
-static inline __attribute__((always_inline)) UIEdgeInsets _LNUserSafeAreas(id self)
+static inline __attribute__((always_inline)) UIEdgeInsets _LNUserSafeAreaInsets(id self)
{
return [objc_getAssociatedObject(self, LNUserAdditionalSafeAreaInsets) UIEdgeInsetsValue];
}
@@ -359,8 +397,8 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
- (UIEdgeInsets)_ln_additionalSafeAreaInsets
{
- UIEdgeInsets user = _LNPopupSafeAreas(self);
- UIEdgeInsets popup = _LNUserSafeAreas(self);
+ UIEdgeInsets user = _LNPopupSafeAreaInsets(self);
+ UIEdgeInsets popup = _LNUserSafeAreaInsets(self);
return __LNEdgeInsetsSum(user, popup);
}
@@ -409,6 +447,18 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
}
}
+- (void)_ln_setNeedsUpdateOfHomeIndicatorAutoHidden
+{
+ if(self.popupPresentationContainerViewController)
+ {
+ [self.popupPresentationContainerViewController setNeedsUpdateOfHomeIndicatorAutoHidden];
+ }
+ else
+ {
+ [self _ln_setNeedsUpdateOfHomeIndicatorAutoHidden];
+ }
+}
+
- (void)_ln_viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator
{
if(self._ln_popupController_nocreate)
@@ -437,21 +487,17 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
}
[self _ln_willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
-}
-
-- (UIViewController*)_findAncestorParentPopupContainerController
-{
- if(self._ln_popupController_nocreate)
- {
- return self;
- }
- if(self.parentViewController == nil)
+ if(@available(iOS 18.0, *))
{
- return nil;
+ if([self isKindOfClass:UITabBarController.class])
+ {
+ [coordinator animateAlongsideTransition:nil completion:^(id _Nonnull context) {
+ static SEL sel = NSSelectorFromString(LNPopupHiddenString("_forceUpdateScrollViewIfNecessary"));
+ [self performSelector:sel];
+ }];
+ }
}
-
- return [self.parentViewController _findAncestorParentPopupContainerController];
}
- (UIViewController*)_findChildInPopupPresentation
@@ -512,6 +558,12 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
return vc ?: [self _ln_childViewControllerForStatusBarStyle];
}
+- (nullable UIViewController *)_ln_common_childViewControllerForHomeIndicatorAutoHidden
+{
+ UIViewController* vc = [self _common_childViewControllersForStatusBarLogic];
+
+ return vc ?: [self _ln_childViewControllerForHomeIndicatorAutoHidden];
+}
- (nullable UIViewController *)_ln_childViewControllerForStatusBarHidden
{
@@ -523,6 +575,11 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
return [self _ln_common_childViewControllerForStatusBarStyle];
}
+- (nullable UIViewController *)_ln_childViewControllerForHomeIndicatorAutoHidden
+{
+ return [self _ln_common_childViewControllerForHomeIndicatorAutoHidden];
+}
+
- (void)_ln_setPopupPresentationState:(LNPopupPresentationState)newState
{
[self willChangeValueForKey:@"popupPresentationState"];
@@ -560,24 +617,13 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
//_updateContentOverlayInsetsFromParentIfNecessary (iOS 15 and above)
- (void)_uCOIFPIN
{
- static SEL contentMarginSEL;
- static SEL setContentMarginSEL;
- static SEL _setContentOverlayInsets_andLeftMargin_rightMarginSEL;
+ static SEL contentMarginSEL = NSSelectorFromString(LNPopupHiddenString("_contentMargin"));
+ static SEL setContentMarginSEL = NSSelectorFromString(LNPopupHiddenString("_setContentMargin:"));
+ static SEL _setContentOverlayInsets_andLeftMargin_rightMarginSEL = NSSelectorFromString(LNPopupHiddenString("_setContentOverlayInsets:andLeftMargin:rightMargin:"));
- static CGFloat (*contentMarginFunc)(id, SEL);
- static void (*setContentMarginFunc)(id, SEL, CGFloat);
- static void (*_setContentOverlayInsets_andLeftMargin_rightMarginFunc)(id, SEL, UIEdgeInsets, CGFloat, CGFloat);
-
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- contentMarginSEL = NSSelectorFromString(_LNPopupDecodeBase64String(cM));
- setContentMarginSEL = NSSelectorFromString(_LNPopupDecodeBase64String(sCM));
- _setContentOverlayInsets_andLeftMargin_rightMarginSEL = NSSelectorFromString(_LNPopupDecodeBase64String(sCOIaLMrM));
-
- contentMarginFunc = (void*)objc_msgSend;
- setContentMarginFunc = (void*)objc_msgSend;
- _setContentOverlayInsets_andLeftMargin_rightMarginFunc = (void*)objc_msgSend;
- });
+ static CGFloat (*contentMarginFunc)(id, SEL) = reinterpret_cast(objc_msgSend);
+ static void (*setContentMarginFunc)(id, SEL, CGFloat) = reinterpret_cast(objc_msgSend);
+ static void (*_setContentOverlayInsets_andLeftMargin_rightMarginFunc)(id, SEL, UIEdgeInsets, CGFloat, CGFloat) = reinterpret_cast(objc_msgSend);
if([self respondsToSelector:@selector(_ln_popupUIRequiresZeroInsets)] && self._ln_popupUIRequiresZeroInsets == YES)
{
@@ -589,11 +635,19 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
[self _uCOIFPIN];
+ if(@available(iOS 17.0, *))
+ {
+ if(__ln_alreadyInHideShowBar && __ln_hideBarEdge == UIRectEdgeBottom)
+ {
+ [self.view layoutIfNeeded];
+ }
+ }
+
if(self.popupPresentationContainerViewController != nil)
{
CGFloat contentMargin = contentMarginFunc(self.popupPresentationContainerViewController, contentMarginSEL);
- UIEdgeInsets insets = __LNEdgeInsetsSum(self.popupPresentationContainerViewController.view.safeAreaInsets, UIEdgeInsetsMake(0, 0, - _LNPopupSafeAreas(self.popupPresentationContainerViewController).bottom, 0));
+ UIEdgeInsets insets = __LNEdgeInsetsSum(self.popupPresentationContainerViewController.view.safeAreaInsets, UIEdgeInsetsMake(0, 0, - _LNPopupSafeAreaInsets(self.popupPresentationContainerViewController).bottom, 0));
_setContentOverlayInsets_andLeftMargin_rightMarginFunc(self, _setContentOverlayInsets_andLeftMargin_rightMarginSEL, insets, contentMargin, contentMargin);
setContentMarginFunc(self, setContentMarginSEL, contentMargin);
@@ -603,13 +657,22 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
self.view.layoutMargins = UIEdgeInsetsMake(0, contentMargin, 0, contentMargin);
}
-#if ! TARGET_OS_MACCATALYST
- if(self.popupContentViewController)
+ if([self.parentViewController isKindOfClass:UIPageViewController.class] && self.parentViewController._isContainedInPopupController)
+ {
+ //Work around Apple bugs
+
+ CGFloat contentMargin = contentMarginFunc(self.parentViewController, contentMarginSEL);
+ UIEdgeInsets insets = self.parentViewController.view.safeAreaInsets;
+
+ _setContentOverlayInsets_andLeftMargin_rightMarginFunc(self, _setContentOverlayInsets_andLeftMargin_rightMarginSEL, insets, contentMargin, contentMargin);
+ setContentMarginFunc(self, setContentMarginSEL, contentMargin);
+ }
+
+ if(LNPopupBar.isCatalystApp && self.popupContentViewController)
{
[self.popupContentViewController _uLFSBAIO];
[self._ln_popupController_nocreate.popupContentView _repositionPopupCloseButton];
}
-#endif
}
@@ -618,7 +681,7 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
{
if([self _isContainedInPopupController])
{
- return __LNEdgeInsetsSum(self.popupPresentationContainerViewController.view.safeAreaInsets, UIEdgeInsetsMake(0, 0, - _LNPopupSafeAreas(self.popupPresentationContainerViewController).bottom, 0));
+ return __LNEdgeInsetsSum(self.popupPresentationContainerViewController.view.safeAreaInsets, UIEdgeInsetsMake(0, 0, - _LNPopupSafeAreaInsets(self.popupPresentationContainerViewController).bottom, 0));
}
UIEdgeInsets insets = [self _vSAIFS];
@@ -643,23 +706,27 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
- (void)_layoutPopupBarOrderForTransition
{
[self._ln_popupController_nocreate.popupBar.superview insertSubview:self._ln_popupController_nocreate.popupBar aboveSubview:self.bottomDockingViewForPopup_internalOrDeveloper];
- [self._ln_popupController_nocreate.popupBar.superview insertSubview:self._ln_bottomBarExtension_nocreate belowSubview:self._ln_popupController_nocreate.popupBar];
+ [self._ln_popupController_nocreate.popupBar.superview insertSubview:self._ln_bottomBarExtension_nocreate belowSubview:self.bottomDockingViewForPopup_internalOrDeveloper];
[self._ln_popupController_nocreate.popupBar.superview insertSubview:self._ln_popupController_nocreate.popupContentView belowSubview:self._ln_popupController_nocreate.popupBar];
}
- (void)_layoutPopupBarOrderForUse
{
- [self.bottomDockingViewForPopup_internalOrDeveloper.superview bringSubviewToFront:self.bottomDockingViewForPopup_internalOrDeveloper];
- if(self._ln_popupController_nocreate.popupBar.resolvedStyle == LNPopupBarStyleFloating)
+ UIView* bottomBar = self.bottomDockingViewForPopup_internalOrDeveloper;
+ LNPopupBar* popupBar = self._ln_popupController_nocreate.popupBar;
+ UIView* parentForPopupBar = bottomBar.superview != nil ? bottomBar.superview : popupBar.superview;
+
+ [bottomBar.superview bringSubviewToFront:bottomBar];
+ if(popupBar.resolvedStyle == LNPopupBarStyleFloating)
{
- [self._ln_popupController_nocreate.popupBar.superview insertSubview:self._ln_popupController_nocreate.popupBar aboveSubview:self.bottomDockingViewForPopup_internalOrDeveloper];
+ [parentForPopupBar insertSubview:popupBar aboveSubview:bottomBar];
}
else
{
- [self._ln_popupController_nocreate.popupBar.superview insertSubview:self._ln_popupController_nocreate.popupBar belowSubview:self.bottomDockingViewForPopup_internalOrDeveloper];
+ [parentForPopupBar insertSubview:popupBar belowSubview:bottomBar];
}
- [self._ln_popupController_nocreate.popupBar.superview insertSubview:self._ln_bottomBarExtension_nocreate belowSubview:self._ln_popupController_nocreate.popupBar];
- [self._ln_popupController_nocreate.popupBar.superview insertSubview:self._ln_popupController_nocreate.popupContentView belowSubview:self._ln_popupController_nocreate.popupBar];
+ [parentForPopupBar insertSubview:self._ln_bottomBarExtension_nocreate belowSubview:popupBar];
+ [parentForPopupBar insertSubview:self._ln_popupController_nocreate.popupContentView aboveSubview:popupBar];
}
- (_LNPopupBarBackgroundView*)_ln_bottomBarExtension_nocreate
@@ -682,7 +749,7 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
{
UIBlurEffectStyle effectStyle;
- if (@available(iOS 13.0, *)) {
+ if(@available(iOS 13.0, *)) {
effectStyle = UIBlurEffectStyleSystemChromeMaterial;
} else {
effectStyle = UIBlurEffectStyleLight;
@@ -745,7 +812,7 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
{
if(self.bottomDockingViewForPopup_nocreateOrDeveloper == self._ln_bottomBarSupport_nocreate)
{
- self._ln_bottomBarSupport_nocreate.frame = self.defaultFrameForBottomDockingView_internalOrDeveloper;
+ self._ln_bottomBarSupport_nocreate.frame = [self _defaultFrameForBottomDockingViewForPopupBar:self._ln_popupController_nocreate.popupBar];
[self.view bringSubviewToFront:self._ln_bottomBarSupport_nocreate];
self._ln_bottomBarExtension.frame = self._ln_bottomBarSupport_nocreate.frame;
@@ -769,6 +836,29 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
{
[self _layoutPopupBarOrderForUse];
}
+
+ if(self._ln_popupController_nocreate.popupControllerInternalState != LNPopupPresentationStateBarHidden)
+ {
+ CGFloat barHeightToUse;
+ if(self._ln_popupController_nocreate.popupControllerPublicState == LNPopupPresentationStateOpen)
+ {
+ barHeightToUse = _LNPopupBarHeightForPopupBar(self.popupBar);
+ }
+ else
+ {
+ barHeightToUse = self.popupBar.frame.size.height;
+ }
+
+ UIEdgeInsets neededInsets = UIEdgeInsetsMake(0, 0, MAX(0, barHeightToUse - [self _ln_popupOffsetForPopupBarStyle:self.popupBar.resolvedStyle]), 0);
+
+ UIEdgeInsets safe = _LNPopupSafeAreaInsets(self);
+ UIEdgeInsets childAdditive = _LNPopupChildAdditiveSafeAreas(self);
+
+ if(neededInsets.bottom != MAX(safe.bottom, childAdditive.bottom))
+ {
+ _LNPopupSupportSetPopupInsetsForViewController(self, YES, neededInsets);
+ }
+ }
}
UIView* extensionView = self._ln_bottomBarExtension_nocreate;
@@ -806,19 +896,19 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
Class superclass = LNDynamicSubclassSuper(self, _LN_UIViewController_AppearanceControl.class);
struct objc_super super = {.receiver = self, .super_class = superclass};
- void (*super_class)(struct objc_super*, SEL, BOOL) = (void*)objc_msgSendSuper;
+ void (*super_class)(struct objc_super*, SEL, BOOL) = reinterpret_cast(objc_msgSendSuper);
super_class(&super, @selector(viewWillAppear:), animated);
__ln_popup_suppressViewControllerLifecycle = NO;
}
-- (void)_userFacing_viewIsAppearing:(BOOL)animated
+- (void)_userFacing_viewIsAppearing:(BOOL)animated API_AVAILABLE(ios(13.0))
{
__ln_popup_suppressViewControllerLifecycle = YES;
Class superclass = LNDynamicSubclassSuper(self, _LN_UIViewController_AppearanceControl.class);
struct objc_super super = {.receiver = self, .super_class = superclass};
- void (*super_class)(struct objc_super*, SEL, BOOL) = (void*)objc_msgSendSuper;
+ void (*super_class)(struct objc_super*, SEL, BOOL) = reinterpret_cast(objc_msgSendSuper);
super_class(&super, @selector(viewIsAppearing:), animated);
__ln_popup_suppressViewControllerLifecycle = NO;
@@ -830,7 +920,7 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
Class superclass = LNDynamicSubclassSuper(self, _LN_UIViewController_AppearanceControl.class);
struct objc_super super = {.receiver = self, .super_class = superclass};
- void (*super_class)(struct objc_super*, SEL, BOOL) = (void*)objc_msgSendSuper;
+ void (*super_class)(struct objc_super*, SEL, BOOL) = reinterpret_cast(objc_msgSendSuper);
super_class(&super, @selector(viewDidAppear:), animated);
__ln_popup_suppressViewControllerLifecycle = NO;
@@ -842,7 +932,7 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
Class superclass = LNDynamicSubclassSuper(self, _LN_UIViewController_AppearanceControl.class);
struct objc_super super = {.receiver = self, .super_class = superclass};
- void (*super_class)(struct objc_super*, SEL, BOOL) = (void*)objc_msgSendSuper;
+ void (*super_class)(struct objc_super*, SEL, BOOL) = reinterpret_cast(objc_msgSendSuper);
super_class(&super, @selector(viewWillDisappear:), animated);
__ln_popup_suppressViewControllerLifecycle = NO;
@@ -854,7 +944,7 @@ UIEdgeInsets _LNPopupChildAdditiveSafeAreas(id self)
Class superclass = LNDynamicSubclassSuper(self, _LN_UIViewController_AppearanceControl.class);
struct objc_super super = {.receiver = self, .super_class = superclass};
- void (*super_class)(struct objc_super*, SEL, BOOL) = (void*)objc_msgSendSuper;
+ void (*super_class)(struct objc_super*, SEL, BOOL) = reinterpret_cast(objc_msgSendSuper);
super_class(&super, @selector(viewDidDisappear:), animated);
__ln_popup_suppressViewControllerLifecycle = NO;
@@ -900,21 +990,33 @@ static void __LNPopupUpdateChildInsets(UIViewController* controller)
_LNSetPopupSafeAreaInsets(controller, popupSafeAreaInsets);
}
-void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller, BOOL layout, UIEdgeInsets popupEdgeInsets)
+void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller, BOOL wantsLayout, UIEdgeInsets popupEdgeInsets)
{
+ BOOL shouldLayout = NO;
+
//Container classes with bottom bars have bugs if additional safe areas are applied directly to them.
//Instead, set a custom property and update their children recursively to take care of the additional safe area.
if(__LNPopupIsClassBuggyForAdditionalSafeArea(controller) == YES)
{
- [controller _ln_setChildAdditiveSafeAreaInsets:popupEdgeInsets];
- __LNPopupUpdateChildInsets(controller);
+ UIEdgeInsets current = _LNPopupChildAdditiveSafeAreas(controller);
+ if(UIEdgeInsetsEqualToEdgeInsets(current, popupEdgeInsets) == NO)
+ {
+ shouldLayout = YES;
+ [controller _ln_setChildAdditiveSafeAreaInsets:popupEdgeInsets];
+ __LNPopupUpdateChildInsets(controller);
+ }
}
else
{
- _LNSetPopupSafeAreaInsets(controller, popupEdgeInsets);
+ UIEdgeInsets current = _LNPopupSafeAreaInsets(controller);
+ if(UIEdgeInsetsEqualToEdgeInsets(current, popupEdgeInsets) == NO)
+ {
+ shouldLayout = YES;
+ _LNSetPopupSafeAreaInsets(controller, popupEdgeInsets);
+ }
}
- if(layout)
+ if(wantsLayout && shouldLayout)
{
[controller.view setNeedsUpdateConstraints];
[controller.view setNeedsLayout];
@@ -927,15 +1029,59 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
@interface UITabBarController (LNPopupSupportPrivate) @end
@implementation UITabBarController (LNPopupSupportPrivate)
+- (void)_layoutPopupBarOrderForUse
+{
+ if(@available(iOS 18.0, *))
+ {
+ LNPopupBar* popupBar = self._ln_popupController_nocreate.popupBar;
+ popupBar._hackyMargins = NSDirectionalEdgeInsetsZero;
+
+ static NSString* outlineViewKey = LNPopupHiddenString("_outlineView");
+ UIView* outlineView = [self.sidebar valueForKey:outlineViewKey];
+
+ if(self.tabBar.superview != nil || outlineView == nil)
+ {
+ [super _layoutPopupBarOrderForUse];
+ [popupBar layoutIfNeeded];
+ return;
+ }
+
+ static NSString* tabContainerViewKey = LNPopupHiddenString("visualStyle.tabContainerView");
+ UIView* parentForPopupBar = [self valueForKeyPath:tabContainerViewKey];
+
+ static NSString* sidebarLayoutKey = LNPopupHiddenString("sidebarLayout");
+
+ NSUInteger sidebarLayout = [[parentForPopupBar valueForKey:sidebarLayoutKey] unsignedIntegerValue];
+
+ if(sidebarLayout == 0)
+ {
+ popupBar._hackyMargins = NSDirectionalEdgeInsetsMake(0, self.sidebar.isHidden ? 0 : outlineView.bounds.size.width, 0, 0);
+ [super _layoutPopupBarOrderForUse];
+ [popupBar layoutIfNeeded];
+ return;
+ }
+
+ [parentForPopupBar insertSubview:popupBar atIndex:0];
+ [parentForPopupBar insertSubview:self._ln_bottomBarExtension_nocreate belowSubview:popupBar];
+ [parentForPopupBar insertSubview:self._ln_popupController_nocreate.popupContentView atIndex:parentForPopupBar.subviews.count];
+
+ [popupBar layoutIfNeeded];
+
+ return;
+ }
+
+ [super _layoutPopupBarOrderForUse];
+}
+
- (BOOL)_isTabBarHiddenDuringTransition
{
NSNumber* isHidden = objc_getAssociatedObject(self, LNToolbarHiddenBeforeTransition);
- return isHidden.boolValue;
+ return isHidden.boolValue || self.tabBar.superview == nil;
}
-- (void)_setTabBarHiddenDuringTransition:(BOOL)toolbarHidden
+- (void)_setTabBarHiddenDuringTransition:(BOOL)tabBarHidden
{
- objc_setAssociatedObject(self, LNToolbarHiddenBeforeTransition, @(toolbarHidden), OBJC_ASSOCIATION_RETAIN);
+ objc_setAssociatedObject(self, LNToolbarHiddenBeforeTransition, @(tabBarHidden), OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)_isPrepareTabBarIgnored
@@ -964,6 +1110,11 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
return self.tabBar.hidden == NO && self._isTabBarHiddenDuringTransition == NO ? UIEdgeInsetsZero : self.view.superview.safeAreaInsets;
}
+- (CGFloat)_ln_popupOffsetForPopupBarStyle:(LNPopupBarStyle)barStyle
+{
+ return self._isTabBarHiddenDuringTransition ? [super _ln_popupOffsetForPopupBarStyle:barStyle] : 0;
+}
+
- (CGRect)defaultFrameForBottomDockingView
{
CGRect bottomBarFrame = self.tabBar.frame;
@@ -983,6 +1134,10 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
@selector(childViewControllerForStatusBarHidden),
@selector(_ln_childViewControllerForStatusBarHidden));
+ LNSwizzleMethod(self,
+ @selector(childViewControllerForHomeIndicatorAutoHidden),
+ @selector(_ln_childViewControllerForHomeIndicatorAutoHidden));
+
LNSwizzleMethod(self,
@selector(viewDidLayoutSubviews),
@selector(_ln_popup_viewDidLayoutSubviews_tvc));
@@ -998,25 +1153,43 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
#ifndef LNPopupControllerEnforceStrictClean
NSString* selName;
- //_hideBarWithTransition:isExplicit:duration:
- selName = _LNPopupDecodeBase64String(hBWTiEDBase64);
- LNSwizzleMethod(self,
- NSSelectorFromString(selName),
- @selector(hBWT:iE:d:));
+ selName = LNPopupHiddenString("_hideBarWithTransition:isExplicit:duration:reason:");
+ if([self instancesRespondToSelector:NSSelectorFromString(selName)])
+ {
+ LNSwizzleMethod(self,
+ NSSelectorFromString(selName),
+ @selector(hBWT:iE:d:r:));
+ }
+ else
+ {
+ selName = LNPopupHiddenString("_hideBarWithTransition:isExplicit:duration:");
+ LNSwizzleMethod(self,
+ NSSelectorFromString(selName),
+ @selector(hBWT:iE:d:));
+ }
- //_showBarWithTransition:isExplicit:duration:
- selName = _LNPopupDecodeBase64String(sBWTiEDBase64);
- LNSwizzleMethod(self,
- NSSelectorFromString(selName),
- @selector(sBWT:iE:d:));
+ selName = LNPopupHiddenString("_showBarWithTransition:isExplicit:duration:reason:");
+ if([self instancesRespondToSelector:NSSelectorFromString(selName)])
+ {
+ //_showBarWithTransition:isExplicit:duration:reason:
+ LNSwizzleMethod(self,
+ NSSelectorFromString(selName),
+ @selector(sBWT:iE:d:r:));
+ }
+ else
+ {
+ selName = LNPopupHiddenString("_showBarWithTransition:isExplicit:duration:");
+ LNSwizzleMethod(self,
+ NSSelectorFromString(selName),
+ @selector(sBWT:iE:d:));
+ }
- //_updateLayoutForStatusBarAndInterfaceOrientation
- selName = _LNPopupDecodeBase64String(uLFSBAIO);
+ selName = LNPopupHiddenString("_updateLayoutForStatusBarAndInterfaceOrientation");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_uLFSBAIO));
- selName = _LNPopupDecodeBase64String(pTBBase64);
+ selName = LNPopupHiddenString("_prepareTabBar");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_ln_pTB));
@@ -1028,7 +1201,7 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
{
if(self._ln_popupController_nocreate.popupControllerInternalState != LNPopupPresentationStateBarHidden)
{
- if(self.tabBar.isHidden == NO && self._isTabBarHiddenDuringTransition == NO && self._ignoringLayoutDuringTransition == NO)
+ if(self.tabBar.isHidden == NO && self._isTabBarHiddenDuringTransition == NO && self._ignoringLayoutDuringTransition == NO && self._ln_isFloatingTabBar == NO)
{
self._ln_bottomBarExtension_nocreate.hidden = YES;
[self._ln_bottomBarExtension_nocreate removeFromSuperview];
@@ -1037,11 +1210,17 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
{
self._ln_popupController_nocreate.popupBar.backgroundView.alpha = 1.0;
}
+
}
else
{
self._ln_bottomBarExtension.hidden = NO;
+ if(self._ln_isFloatingTabBar == YES)
+ {
+ self._ln_bottomBarExtension_nocreate.alpha = 1.0;
+ }
+
if(self._ln_popupController_nocreate.popupBar.resolvedStyle == LNPopupBarStyleFloating && self._ignoringLayoutDuringTransition == NO)
{
self._ln_popupController_nocreate.popupBar.backgroundView.alpha = 0.0;
@@ -1059,7 +1238,9 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
if(self._ignoringLayoutDuringTransition == NO)
{
CGFloat bottomSafeArea = self.view.superview.safeAreaInsets.bottom;
- self._ln_bottomBarExtension_nocreate.frame = CGRectMake(0, self.view.bounds.size.height - bottomSafeArea, self.view.bounds.size.width, bottomSafeArea);
+ CGRect frame = CGRectMake(0, self.view.bounds.size.height - bottomSafeArea, self.view.bounds.size.width, bottomSafeArea);
+ UIEdgeInsets hackyInsets = _LNEdgeInsetsFromDirectionalEdgeInsets(self._ln_popupController_nocreate.popupBar, self._ln_popupController_nocreate.popupBar._hackyMargins);
+ self._ln_bottomBarExtension_nocreate.frame = UIEdgeInsetsInsetRect(frame, hackyInsets);
}
}
@@ -1107,9 +1288,122 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
self._ln_popupController_nocreate.popupBar.frame = frame;
}
+- (void)_ln_animateAlongsideTransition:(NSUInteger)transition withDuration:(NSTimeInterval)duration animations:(void (^ __nullable)(id context))animations completion:(void (^ __nullable)(id context))completion
+{
+ id transitionCoordinator = self.selectedViewController.transitionCoordinator;
+
+ __weak __typeof(self) weakSelf = self;
+
+ if(transitionCoordinator != nil)
+ {
+ [transitionCoordinator animateAlongsideTransition:animations completion:^(id _Nonnull context) {
+ if(completion != nil)
+ {
+ completion(context);
+ }
+ }];
+ }
+ else
+ {
+ __LNFakeContext* ctx = [__LNFakeContext new];
+ ctx.cancelled = NO;
+ if(duration != 0)
+ {
+ if(duration == -1)
+ {
+ duration = __ln_durationForTransition(self, transition);
+ }
+
+ [UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionLayoutSubviews animations:^{
+ if(animations != nil)
+ {
+ animations((id)ctx);
+ }
+ } completion:^(BOOL finished) {
+ if(completion != nil)
+ {
+ completion((id)ctx);
+ }
+ }];
+ }
+ else
+ {
+ [UIView performWithoutAnimation:^{
+ if(animations != nil)
+ {
+ animations((id)ctx);
+ }
+ if(completion != nil)
+ {
+ completion((id)ctx);
+ }
+ }];
+ }
+ }
+}
+
+- (BOOL)_ln_isFloatingTabBar
+{
+ if(unavailable(iOS 18.0, *))
+ {
+ return NO;
+ }
+
+ if(self.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad && self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular)
+ {
+ return YES;
+ }
+
+ return NO;
+}
+
//_hideBarWithTransition:isExplicit:duration:
- (void)hBWT:(NSInteger)t iE:(BOOL)e d:(NSTimeInterval)duration
{
+ [self hBWT:t iE:e d:duration r:NSUIntegerMax];
+}
+
+//_hideBarWithTransition:isExplicit:duration:reason:
+- (void)hBWT:(NSInteger)transition iE:(BOOL)isExplicit d:(NSTimeInterval)duration r:(NSUInteger)reason
+{
+ if(self._ln_popupController_nocreate.popupControllerInternalState == LNPopupPresentationStateBarHidden || self._ln_isFloatingTabBar == YES)
+ {
+ [self _setTabBarHiddenDuringTransition:YES];
+
+ if(@available(iOS 18.0, *))
+ {
+ [self hBWT:transition iE:isExplicit d:duration r:reason];
+ }
+ else
+ {
+ [self hBWT:transition iE:isExplicit d:duration];
+ }
+
+ [self _ln_animateAlongsideTransition:transition withDuration:duration animations:^(id context) {
+ if(transition != 1)
+ {
+ [self _ln_updateSafeAreaInsets];
+ [self.view layoutIfNeeded];
+ }
+ } completion:nil];
+
+ return;
+ }
+
+ if(__ln_alreadyInHideShowBar == YES)
+ {
+ //Ignore nested calls to _hideBarWithTransition:isExplicit:duration:reason:
+ if(@available(iOS 18.0, *))
+ {
+ [self hBWT:transition iE:isExplicit d:duration r:reason];
+ }
+ else
+ {
+ [self hBWT:transition iE:isExplicit d:duration];
+ }
+ return;
+ }
+
BOOL isFloating = self._ln_popupController_nocreate.popupBar.resolvedStyle == LNPopupBarStyleFloating;
if(!isFloating)
{
@@ -1123,25 +1417,64 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
[self _setTabBarHiddenDuringTransition:YES];
CGRect frame = self.tabBar.frame;
- if(t != 0)
+ if(transition == 1)
{
frame.origin.x = (isRTL ? -1 : 1) * self.view.bounds.size.width;
}
self._ln_bottomBarExtension.frame = frame;
self._ln_bottomBarExtension_nocreate.hidden = NO;
self._ln_bottomBarExtension_nocreate.alpha = 1.0;
- [self hBWT:t iE:e d:duration];
+
+ [self._ln_bottomBarExtension layoutIfNeeded];
+
+ __ln_alreadyInHideShowBar = YES;
+ if(@available(iOS 18.0, *))
+ {
+ [self hBWT:transition iE:isExplicit d:duration r:reason];
+ }
+ else
+ {
+ [self hBWT:transition iE:isExplicit d:duration];
+ }
+ __ln_alreadyInHideShowBar = NO;
+
+ if(transition != 1 && isExplicit == NO)
+ {
+ return;
+ }
NSString* effectGroupingIdentifier = self._ln_popupController_nocreate.popupBar.effectGroupingIdentifier;
- self._ln_popupController_nocreate.popupBar.effectGroupingIdentifier = nil;
+ NSString* traitOverride = nil;
+
+ if(@available(iOS 17.0, *))
+ {
+ traitOverride = [self._ln_popupController_nocreate.bottomBar.traitCollection objectForTrait:_LNPopupBarBackgroundGroupNameOverride.class];
+ }
+
+ if(transition == 1)
+ {
+ if(@available(iOS 17.0, *))
+ {
+ [self._ln_popupController_nocreate.bottomBar.traitOverrides setObject:nil forTrait:_LNPopupBarBackgroundGroupNameOverride.class];
+ }
+ else
+ {
+ self._ln_popupController_nocreate.popupBar.effectGroupingIdentifier = nil;
+ }
+ }
+ else
+ {
+ self._ln_popupController_nocreate.popupBar.bottomShadowView.alpha = 0.0;
+ }
self._ln_popupController_nocreate.popupBar.wantsBackgroundCutout = NO;
- if(t == 1 && isFloating)
+ if(transition == 1 && isFloating)
{
self._ln_popupController_nocreate.popupBar.backgroundView.transitionShadingView.alpha = 0.0;
self._ln_popupController_nocreate.popupBar.backgroundView.transitionShadingView.hidden = NO;
}
+ [self.tabBar _setIgnoringLayoutDuringTransition:YES];
[self _setIgnoringLayoutDuringTransition:YES];
CGFloat bottomSafeArea = self.view.superview.safeAreaInsets.bottom;
@@ -1153,26 +1486,45 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
self._ln_bottomBarExtension_nocreate.alpha = 1.0;
void (^animations)(id) = ^ (id _Nonnull context) {
+ if(transition != 1)
+ {
+ [self _ln_updateSafeAreaInsets];
+ [self.view layoutIfNeeded];
+ }
+
self._ln_bottomBarExtension_nocreate.frame = CGRectMake(0, self.view.bounds.size.height - bottomSafeArea, self.view.bounds.size.width, self._ln_bottomBarExtension_nocreate.frame.size.height);
- self._ln_popupController_nocreate.popupBar.bottomShadowView.alpha = 0.0;
+
+ if(transition == 1)
+ {
+ self._ln_popupController_nocreate.popupBar.bottomShadowView.alpha = 0.0;
+ }
[self __repositionPopupBarToClosed_hack];
+
if(isFloating)
{
[self._ln_popupController_nocreate.popupBar layoutIfNeeded];
- self._ln_popupController_nocreate.popupBar.backgroundView.frame = CGRectOffset(backgroundViewFrame, (isRTL ? 1 : -1) * CGRectGetWidth(backgroundViewFrame), -CGRectGetHeight(frame) + bottomSafeArea);
self._ln_popupController_nocreate.popupBar.backgroundView.alpha = 1.0;
- if(t == 1)
+
+ if(transition == 1)
{
+ self._ln_popupController_nocreate.popupBar.backgroundView.frame = CGRectOffset(backgroundViewFrame, (isRTL ? 1 : -1) * CGRectGetWidth(backgroundViewFrame), -CGRectGetHeight(frame) + bottomSafeArea);
self._ln_popupController_nocreate.popupBar.backgroundView.transitionShadingView.alpha = 1.0;
}
+ else
+ {
+ self._ln_popupController_nocreate.popupBar.backgroundView.frame = CGRectOffset(backgroundViewFrame, 0, bottomSafeArea);
+ self._ln_popupController_nocreate.popupBar.backgroundView.alpha = 0.0;
+ }
}
};
void (^completion)(id) = ^ (id _Nonnull context) {
+ [self.tabBar _setIgnoringLayoutDuringTransition:NO];
+
if(isFloating)
{
- if(t == 1)
+ if(transition == 1)
{
self._ln_popupController_nocreate.popupBar.backgroundView.transitionShadingView.alpha = 0.0;
self._ln_popupController_nocreate.popupBar.backgroundView.transitionShadingView.hidden = YES;
@@ -1184,57 +1536,72 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
self._ln_bottomBarExtension_nocreate.frame = CGRectMake(0, self.view.bounds.size.height - bottomSafeArea, self.view.bounds.size.width, bottomSafeArea);
[self _setIgnoringLayoutDuringTransition:NO];
+ [self._ln_popupController_nocreate _popupBarMetricsDidChange:self._ln_popupController_nocreate.popupBar shouldLayout:NO];
[self._ln_popupController_nocreate _setContentToState:self._ln_popupController_nocreate.popupControllerInternalState];
self._ln_popupController_nocreate.popupBar.bottomShadowView.hidden = YES;
self._ln_popupController_nocreate.popupBar.bottomShadowView.alpha = 1.0;
- self._ln_popupController_nocreate.popupBar.effectGroupingIdentifier = effectGroupingIdentifier;
+ if(@available(iOS 17.0, *))
+ {
+ [self._ln_popupController_nocreate.bottomBar.traitOverrides setObject:traitOverride forTrait:_LNPopupBarBackgroundGroupNameOverride.class];
+ }
+ else
+ {
+ self._ln_popupController_nocreate.popupBar.effectGroupingIdentifier = effectGroupingIdentifier;
+ }
self._ln_popupController_nocreate.popupBar.backgroundView.frame = backgroundViewFrame;
[self _layoutPopupBarOrderForUse];
};
- id transitionCoordinator = self.selectedViewController.transitionCoordinator;
-
- if(transitionCoordinator != nil)
- {
- [transitionCoordinator animateAlongsideTransition:animations completion:completion];
- }
- else
- {
- [UIView performWithoutAnimation:^{
- animations(nil);
- completion(nil);
- }];
- }
+ [self _ln_animateAlongsideTransition:transition withDuration:duration animations:animations completion:completion];
}
//_showBarWithTransition:isExplicit:duration:
- (void)sBWT:(NSInteger)t iE:(BOOL)e d:(NSTimeInterval)duration
{
+ [self sBWT:t iE:e d:duration r:NSUIntegerMax];
+}
+
+//_showBarWithTransition:isExplicit:duration:reason:
+- (void)sBWT:(NSInteger)transition iE:(BOOL)isExplicit d:(NSTimeInterval)duration r:(NSUInteger)reason
+{
+ if(__ln_alreadyInHideShowBar == YES)
+ {
+ //Ignore nested calls to _showBarWithTransition:isExplicit:duration:
+ if(@available(iOS 18.0, *))
+ {
+ [self sBWT:transition iE:isExplicit d:duration r:reason];
+ }
+ else
+ {
+ [self sBWT:transition iE:isExplicit d:duration];
+ }
+ return;
+ }
+
BOOL isFloating = self._ln_popupController_nocreate.popupBar.resolvedStyle == LNPopupBarStyleFloating;
BOOL isRTL = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.tabBar.superview.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft;
- if(e == YES)
- {
- if(!isFloating)
- {
- self._ln_popupController_nocreate.popupBar.bottomShadowView.hidden = NO;
- }
-
- [self _setPrepareTabBarIgnored:YES];
- }
-
[self._ln_popupController_nocreate.popupBar _cancelGestureRecognizers];
- BOOL wasHidden = self.tabBar.isHidden;
+ BOOL wasHidden = self.tabBar.isHidden || self._isTabBarHiddenDuringTransition;
- [self sBWT:t iE:e d:duration];
+ __ln_alreadyInHideShowBar = YES;
+ if(@available(iOS 18.0, *))
+ {
+ [self sBWT:transition iE:isExplicit d:duration r:reason];
+ }
+ else
+ {
+ [self sBWT:transition iE:isExplicit d:duration];
+ }
+ __ln_alreadyInHideShowBar = NO;
- if(e == NO)
+ if(isExplicit == NO)
{
return;
}
@@ -1244,45 +1611,92 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
return;
}
- self._ln_popupController_nocreate.popupBar.bottomShadowView.alpha = 0.0;
- self._ln_popupController_nocreate.popupBar.backgroundView.alpha = 1.0;
-
- if(wasHidden == YES)
+ if(self._ln_isFloatingTabBar == YES)
{
- self._ln_popupController_nocreate.popupBar.wantsBackgroundCutout = NO;
+ [self.view setNeedsLayout];
+ [self _setTabBarHiddenDuringTransition:NO];
+
+ return;
}
- if(t == 2 && isFloating)
+ if(!isFloating)
{
+ self._ln_popupController_nocreate.popupBar.bottomShadowView.hidden = NO;
+ }
+
+ [self _setPrepareTabBarIgnored:YES];
+
+ self._ln_popupController_nocreate.popupBar.bottomShadowView.alpha = 0.0;
+
+ self._ln_popupController_nocreate.popupBar.wantsBackgroundCutout = NO;
+
+ __block CGRect frame = self.tabBar.frame;
+
+ __block CGRect backgroundViewFrame = self._ln_popupController_nocreate.popupBar.backgroundView.frame;
+ CGFloat bottomSafeArea = self.view.superview.safeAreaInsets.bottom;
+ if(transition == 2 && isFloating)
+ {
+ self._ln_popupController_nocreate.popupBar.backgroundView.alpha = 1.0;
self._ln_popupController_nocreate.popupBar.backgroundView.transitionShadingView.alpha = 1.0;
self._ln_popupController_nocreate.popupBar.backgroundView.transitionShadingView.hidden = NO;
+
+ CGRect initial = CGRectOffset(backgroundViewFrame, (isRTL ? 1 : -1) * CGRectGetWidth(backgroundViewFrame), -CGRectGetHeight(frame) + bottomSafeArea);
+
+ self._ln_popupController_nocreate.popupBar.backgroundView.frame = initial;
}
-
- CGRect backgroundViewFrame = self._ln_popupController_nocreate.popupBar.backgroundView.frame;
- if(isFloating && wasHidden == YES)
+ else if(isFloating)
{
- self._ln_popupController_nocreate.popupBar.backgroundView.frame = CGRectOffset(backgroundViewFrame, (isRTL ? 1 : -1) * CGRectGetWidth(backgroundViewFrame), -CGRectGetHeight(self.tabBar.frame) + self.view.superview.safeAreaInsets.bottom);
+ self._ln_popupController_nocreate.popupBar.backgroundView.frame = CGRectOffset(backgroundViewFrame, 0, bottomSafeArea);
}
- __block CGRect frame = self.tabBar.frame;
[self _setIgnoringLayoutDuringTransition:YES];
NSString* effectGroupingIdentifier = self._ln_popupController_nocreate.popupBar.effectGroupingIdentifier;
- self._ln_popupController_nocreate.popupBar.effectGroupingIdentifier = nil;
+ NSString* traitOverride = nil;
+
+ if(@available(iOS 17.0, *))
+ {
+ traitOverride = [self._ln_popupController_nocreate.bottomBar.traitCollection objectForTrait:_LNPopupBarBackgroundGroupNameOverride.class];
+ }
+
+ if(transition == 2)
+ {
+ if(@available(iOS 17.0, *))
+ {
+ [self._ln_popupController_nocreate.bottomBar.traitOverrides setObject:nil forTrait:_LNPopupBarBackgroundGroupNameOverride.class];
+ }
+ else
+ {
+ self._ln_popupController_nocreate.popupBar.effectGroupingIdentifier = nil;
+ }
+ }
void (^animations)(id) = ^ (id _Nonnull context) {
+ if(transition != 2)
+ {
+ [self _ln_updateSafeAreaInsets];
+ [self.view layoutIfNeeded];
+ }
+
[UIView performWithoutAnimation:^{
self.tabBar.frame = frame;
}];
- frame.origin.x += (isRTL ? -1 : 1) * self.view.bounds.size.width;
+ if(transition == 2)
+ {
+ frame.origin.x += (isRTL ? -1 : 1) * self.view.bounds.size.width;
+ }
self._ln_bottomBarExtension.frame = frame;
- self._ln_popupController_nocreate.popupBar.bottomShadowView.alpha = 1.0;
- if(isFloating && wasHidden == YES)
+
+ if(transition == 2)
+ {
+ self._ln_popupController_nocreate.popupBar.bottomShadowView.alpha = 1.0;
+ }
+ if(isFloating)
{
self._ln_popupController_nocreate.popupBar.backgroundView.frame = backgroundViewFrame;
self._ln_popupController_nocreate.popupBar.backgroundView.alpha = 1.0;
- if(t == 2)
+ if(transition == 2)
{
self._ln_popupController_nocreate.popupBar.backgroundView.transitionShadingView.alpha = 0.0;
}
@@ -1296,12 +1710,9 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
void (^completion)(id) = ^ (id _Nonnull context) {
[self _setPrepareTabBarIgnored:NO];
- if(wasHidden == YES)
- {
- [self._ln_popupController_nocreate.popupBar setWantsBackgroundCutout:YES allowImplicitAnimations:YES];
- }
+ [self._ln_popupController_nocreate.popupBar setWantsBackgroundCutout:YES allowImplicitAnimations:YES];
- if(t == 2 && isFloating)
+ if(transition == 2 && isFloating)
{
self._ln_popupController_nocreate.popupBar.backgroundView.transitionShadingView.alpha = 0.0;
self._ln_popupController_nocreate.popupBar.backgroundView.transitionShadingView.hidden = YES;
@@ -1320,13 +1731,21 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
self._ln_popupController_nocreate.popupBar.bottomShadowView.hidden = YES;
self._ln_popupController_nocreate.popupBar.bottomShadowView.alpha = 1.0;
+ [self._ln_popupController_nocreate _popupBarMetricsDidChange:self._ln_popupController_nocreate.popupBar shouldLayout:NO];
[self._ln_popupController_nocreate _setContentToState:self._ln_popupController_nocreate.popupControllerInternalState];
[self _layoutPopupBarOrderForUse];
[self _setIgnoringLayoutDuringTransition:NO];
- self._ln_popupController_nocreate.popupBar.effectGroupingIdentifier = effectGroupingIdentifier;
+ if(@available(iOS 17.0, *))
+ {
+ [self._ln_popupController_nocreate.bottomBar.traitOverrides setObject:traitOverride forTrait:_LNPopupBarBackgroundGroupNameOverride.class];
+ }
+ else
+ {
+ self._ln_popupController_nocreate.popupBar.effectGroupingIdentifier = effectGroupingIdentifier;
+ }
if(context.isCancelled == NO)
{
@@ -1334,21 +1753,7 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
}
};
- id transitionCoordinator = self.selectedViewController.transitionCoordinator;
-
- if(transitionCoordinator != nil)
- {
- [transitionCoordinator animateAlongsideTransition:animations completion:completion];
- }
- else
- {
- [UIView performWithoutAnimation:^{
- __LNFakeContext* ctx = [__LNFakeContext new];
- ctx.cancelled = NO;
- animations((id)ctx);
- completion((id)ctx);
- }];
- }
+ [self _ln_animateAlongsideTransition:transition withDuration:duration animations:animations completion:completion];
}
//_prepareTabBar
@@ -1366,6 +1771,16 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
self.tabBar.frame = oldBarFrame;
}
}
+
+//updateTabBarLayout
+- (void)_ln_uTBL
+{
+ if(self._ignoringLayoutDuringTransition == NO)
+ {
+ [self _ln_uTBL];
+ }
+}
+
#endif
- (nullable UIViewController *)_ln_childViewControllerForStatusBarHidden
@@ -1378,6 +1793,11 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
return [self _ln_common_childViewControllerForStatusBarStyle];
}
+- (nullable UIViewController *)_ln_childViewControllerForHomeIndicatorAutoHidden
+{
+ return [self _ln_common_childViewControllerForHomeIndicatorAutoHidden];
+}
+
@end
#pragma mark - UINavigationController
@@ -1395,16 +1815,24 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
return self.toolbar;
}
+- (CGFloat)_ln_popupOffsetForPopupBarStyle:(LNPopupBarStyle)barStyle
+{
+ return self.isToolbarHidden ? [super _ln_popupOffsetForPopupBarStyle:barStyle] : 0;
+}
+
- (CGRect)defaultFrameForBottomDockingView
{
CGRect toolbarBarFrame = self.toolbar.frame;
- CGFloat bottomSafeAreaHeight = self.view.safeAreaInsets.bottom;
- if([NSStringFromClass(self.nonMemoryLeakingPresentationController.class) containsString:@"Preview"] == NO)
+ CGFloat bottomSafeAreaHeight = 0.0;
+ if(unavailable(iOS 18.0, *))
{
- bottomSafeAreaHeight -= self.view.window.safeAreaInsets.bottom;
+ bottomSafeAreaHeight = self.view.safeAreaInsets.bottom;
+ if([NSStringFromClass(self.nonMemoryLeakingPresentationController.class) containsString:@"Preview"] == NO)
+ {
+ bottomSafeAreaHeight -= self.view.window.safeAreaInsets.bottom;
+ }
}
-
toolbarBarFrame.origin = CGPointMake(toolbarBarFrame.origin.x, self.view.bounds.size.height - (self.isToolbarHidden ? 0.0 : toolbarBarFrame.size.height) - bottomSafeAreaHeight);
return toolbarBarFrame;
@@ -1412,12 +1840,31 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
- (UIEdgeInsets)insetsForBottomDockingView
{
- if(self.presentingViewController != nil && [NSStringFromClass(self.nonMemoryLeakingPresentationController.class) containsString:@"Preview"])
+ if(@available(iOS 18.0, *))
{
- return UIEdgeInsetsZero;
+ CGFloat offset = 0;
+
+ if(UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad)
+ {
+ static auto key = LNPopupHiddenString("_backgroundView.bounds");
+ if([[self.toolbar valueForKeyPath:key] CGRectValue].size.height < (self.toolbar.bounds.size.height + self.view.safeAreaInsets.bottom))
+ {
+ //Something in UIKit reports safe area insets incorrectly on iPadOS. This is a workaround for this issue.
+ offset -= 5;
+ }
+ }
+
+ return UIEdgeInsetsMake(0, 0, self.view.safeAreaInsets.bottom + offset, 0);
+ }
+ else
+ {
+ if(self.presentingViewController != nil && [NSStringFromClass(self.nonMemoryLeakingPresentationController.class) containsString:@"Preview"])
+ {
+ return UIEdgeInsetsZero;
+ }
+
+ return UIEdgeInsetsMake(0, 0, MAX(self.view.superview.safeAreaInsets.bottom, self.view.window.safeAreaInsets.bottom), 0);
}
-
- return UIEdgeInsetsMake(0, 0, MAX(self.view.superview.safeAreaInsets.bottom, self.view.window.safeAreaInsets.bottom), 0);
}
+ (void)load
@@ -1432,6 +1879,10 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
@selector(childViewControllerForStatusBarHidden),
@selector(_ln_childViewControllerForStatusBarHidden));
+ LNSwizzleMethod(self,
+ @selector(childViewControllerForHomeIndicatorAutoHidden),
+ @selector(_ln_childViewControllerForHomeIndicatorAutoHidden));
+
LNSwizzleMethod(self,
@selector(setNavigationBarHidden:animated:),
@selector(_ln_setNavigationBarHidden:animated:));
@@ -1463,20 +1914,17 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
#ifndef LNPopupControllerEnforceStrictClean
NSString* selName;
- //_setToolbarHidden:edge:duration:
- selName = _LNPopupDecodeBase64String(sTHedBase64);
+ selName = LNPopupHiddenString("_setToolbarHidden:edge:duration:");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_sTH:e:d:));
- //_hideShowNavigationBarDidStop:finished:context:
- selName = _LNPopupDecodeBase64String(hSNBDSfcBase64);
+ selName = LNPopupHiddenString("_hideShowNavigationBarDidStop:finished:context:");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(hSNBDS:f:c:));
- //_updateLayoutForStatusBarAndInterfaceOrientation
- selName = _LNPopupDecodeBase64String(uLFSBAIO);
+ selName = LNPopupHiddenString("_updateLayoutForStatusBarAndInterfaceOrientation");
LNSwizzleMethod(self,
NSSelectorFromString(selName),
@selector(_uLFSBAIO));
@@ -1609,8 +2057,19 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
BOOL wasToolbarHidden = self.isToolbarHidden;
+ if(edge == UIRectEdgeBottom && wasToolbarHidden != hidden)
+ {
+ [self _setIgnoringLayoutDuringTransition:YES];
+ }
+
+ CGFloat earlyBarOffset = [self _ln_popupOffsetForPopupBarStyle:self._ln_popupController_nocreate.popupBar.resolvedStyle];
+
+ __ln_hideBarEdge = edge;
+ __ln_alreadyInHideShowBar = YES;
//Trigger the toolbar hide or show transition.
[self _sTH:hidden e:edge d:duration];
+ __ln_alreadyInHideShowBar = NO;
+ __ln_hideBarEdge = UIRectEdgeNone;
if(wasToolbarHidden != hidden)
{
@@ -1628,37 +2087,40 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
self._ln_bottomBarExtension_nocreate.alpha = 1.0;
}
+ CGFloat bottomSafeArea = self.view.superview.safeAreaInsets.bottom;
CGRect backgroundViewFrame = self._ln_popupController_nocreate.popupBar.backgroundView.frame;
CGRect initialBackgroundViewFrame;
CGRect targetBackgroundViewFrame;
+ CGFloat laterBarOffset = [self _ln_popupOffsetForPopupBarStyle:self._ln_popupController_nocreate.popupBar.resolvedStyle];
+
if(edge == UIRectEdgeBottom)
{
if(hidden == YES)
{
initialBackgroundViewFrame = backgroundViewFrame;
- targetBackgroundViewFrame = CGRectOffset(backgroundViewFrame, 0, CGRectGetHeight(frame));
+ targetBackgroundViewFrame = CGRectOffset(backgroundViewFrame, 0, bottomSafeArea - laterBarOffset);
}
else
{
- initialBackgroundViewFrame = CGRectOffset(backgroundViewFrame, 0, CGRectGetHeight(frame));
+ initialBackgroundViewFrame = CGRectOffset(backgroundViewFrame, 0, bottomSafeArea - earlyBarOffset);
targetBackgroundViewFrame = backgroundViewFrame;
}
}
else if(hidden == YES)
{
initialBackgroundViewFrame = backgroundViewFrame;
- targetBackgroundViewFrame = CGRectOffset(backgroundViewFrame, (edge == UIRectEdgeRight ? 1 : -1) * CGRectGetWidth(backgroundViewFrame), -CGRectGetHeight(frame) + self.view.superview.safeAreaInsets.bottom);
+ targetBackgroundViewFrame = CGRectOffset(backgroundViewFrame, (edge == UIRectEdgeRight ? 1 : -1) * CGRectGetWidth(backgroundViewFrame), -CGRectGetHeight(frame) + bottomSafeArea - laterBarOffset);
}
else
{
- initialBackgroundViewFrame = CGRectOffset(backgroundViewFrame, (edge == UIRectEdgeRight ? 1 : -1) * CGRectGetWidth(backgroundViewFrame), -CGRectGetHeight(frame) + self.view.superview.safeAreaInsets.bottom);
+ initialBackgroundViewFrame = CGRectOffset(backgroundViewFrame, (edge == UIRectEdgeRight ? 1 : -1) * CGRectGetWidth(backgroundViewFrame), -CGRectGetHeight(frame) + bottomSafeArea - earlyBarOffset);
targetBackgroundViewFrame = backgroundViewFrame;
}
if(isFloating)
{
- self._ln_popupController_nocreate.popupBar.backgroundView.alpha = 1.0;
+ self._ln_popupController_nocreate.popupBar.backgroundView.alpha = (hidden == YES || edge != UIRectEdgeBottom) ? 1.0 : 0.0;
self._ln_popupController_nocreate.popupBar.backgroundView.frame = initialBackgroundViewFrame;
}
@@ -1669,6 +2131,7 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
[self _layoutPopupBarOrderForTransition];
void (^animations)(void) = ^ {
+ [self._ln_popupController_nocreate _popupBarMetricsDidChange:self._ln_popupController_nocreate.popupBar shouldLayout:NO];
//During the transition, animate the popup bar and content together with the toolbar transition.
[self._ln_popupController_nocreate _setContentToState:self._ln_popupController_nocreate.popupControllerInternalState];
@@ -1686,7 +2149,7 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
if(isFloating)
{
- self._ln_popupController_nocreate.popupBar.backgroundView.alpha = 1.0;
+ self._ln_popupController_nocreate.popupBar.backgroundView.alpha = edge == UIRectEdgeBottom ? 0.0 : 1.0;
}
}
else
@@ -1734,12 +2197,13 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
self._ln_popupController_nocreate.popupBar.backgroundView.frame = backgroundViewFrame;
- [self _layoutPopupBarOrderForUse];
-
[self _setIgnoringLayoutDuringTransition:NO];
};
- [self _setIgnoringLayoutDuringTransition:YES];
+ if(edge != UIRectEdgeBottom)
+ {
+ [self _setIgnoringLayoutDuringTransition:YES];
+ }
if(duration == 0)
{
@@ -1787,6 +2251,11 @@ void _LNPopupSupportSetPopupInsetsForViewController(UIViewController* controller
return [self _ln_common_childViewControllerForStatusBarStyle];
}
+- (nullable UIViewController *)_ln_childViewControllerForHomeIndicatorAutoHidden
+{
+ return [self _ln_common_childViewControllerForHomeIndicatorAutoHidden];
+}
+
- (void)_ln_setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated
{
[self _ln_setNavigationBarHidden:hidden animated:animated];
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupAddressInfo.h b/LNPopupController/LNPopupController/Private/_LNPopupAddressInfo.h
new file mode 100644
index 0000000..ca7ea14
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupAddressInfo.h
@@ -0,0 +1,24 @@
+//
+// _LNPopupAddressInfo.h
+// LNPopupController
+//
+// Created by Léo Natan on 2024-08-09.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface _LNPopupAddressInfo : NSObject
+
+- (instancetype)initWithAddress:(NSUInteger)address;
+
+@property (nonatomic, readonly) NSUInteger address;
+@property (nonatomic, copy, readonly) NSString* image;
+@property (nonatomic, copy, readonly) NSString* symbol;
+@property (nonatomic, readonly) NSUInteger offset;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupAddressInfo.mm b/LNPopupController/LNPopupController/Private/_LNPopupAddressInfo.mm
new file mode 100644
index 0000000..2e9ccfb
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupAddressInfo.mm
@@ -0,0 +1,86 @@
+//
+// _LNPopupAddressInfo.mm
+// LNPopupController
+//
+// Created by Léo Natan on 2024-08-09.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import "_LNPopupAddressInfo.h"
+#include
+#include
+
+@implementation _LNPopupAddressInfo
+{
+ Dl_info _info;
+}
+
+@synthesize image, symbol, offset, address;
+
+- (instancetype)initWithAddress:(NSUInteger)_address
+{
+ self = [super init];
+
+ if(self)
+ {
+ address = _address;
+ dladdr((void*)address, &_info);
+ }
+
+ return self;
+}
+
+- (NSString *)image
+{
+ if(_info.dli_fname != NULL)
+ {
+ NSString* potentialImage = [NSString stringWithUTF8String:_info.dli_fname];
+
+ if([potentialImage containsString:@"/"])
+ {
+ return potentialImage.lastPathComponent;
+ }
+ }
+
+ return @"???";
+}
+
+- (NSString *)symbol
+{
+ if(_info.dli_sname != NULL)
+ {
+ return [NSString stringWithUTF8String:_info.dli_sname];
+ }
+ else if(_info.dli_fname != NULL)
+ {
+ return self.image;
+ }
+
+ return [NSString stringWithFormat:@"0x%1lx", (unsigned long)_info.dli_saddr];
+}
+
+- (NSUInteger)offset
+{
+ NSString* str = nil;
+ if(_info.dli_sname != NULL && (str = [NSString stringWithUTF8String:_info.dli_sname]) != nil)
+ {
+ return address - (NSUInteger)_info.dli_saddr;
+ }
+ else if(_info.dli_fname != NULL && (str = [NSString stringWithUTF8String:_info.dli_fname]) != nil)
+ {
+ return address - (NSUInteger)_info.dli_fbase;
+ }
+
+ return address - (NSUInteger)_info.dli_saddr;
+}
+
+- (NSString*)description
+{
+#if __LP64__
+ return [NSString stringWithFormat:@"%-35s 0x%016llx %@ + %ld", self.image.UTF8String, (uint64_t)address, self.symbol, self.offset];
+#else
+ return [NSString stringWithFormat:@"%-35s 0x%08lx %@ + %d", self.image.UTF8String, (unsigned long)address, self.symbol, self.offset];
+#endif
+}
+
+@end
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupBackgroundShadowView.h b/LNPopupController/LNPopupController/Private/_LNPopupBackgroundShadowView.h
index 3e57086..896bea7 100644
--- a/LNPopupController/LNPopupController/Private/_LNPopupBackgroundShadowView.h
+++ b/LNPopupController/LNPopupController/Private/_LNPopupBackgroundShadowView.h
@@ -2,8 +2,8 @@
// _LNPopupBackgroundShadowView.h
// LNPopupController
//
-// Created by Leo Natan on 24/09/2023.
-// Copyright © 2023 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2023-09-25.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupBackgroundShadowView.m b/LNPopupController/LNPopupController/Private/_LNPopupBackgroundShadowView.m
index 4e035dc..b3b5fae 100644
--- a/LNPopupController/LNPopupController/Private/_LNPopupBackgroundShadowView.m
+++ b/LNPopupController/LNPopupController/Private/_LNPopupBackgroundShadowView.m
@@ -2,8 +2,8 @@
// _LNPopupBackgroundShadowView.m
// LNPopupController
//
-// Created by Leo Natan on 24/09/2023.
-// Copyright © 2023 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2023-09-25.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "_LNPopupBackgroundShadowView.h"
@@ -11,7 +11,6 @@
@implementation _LNPopupBackgroundShadowView
{
CAShapeLayer* _maskLayer;
- UIColor* _color;
}
- (instancetype)initWithFrame:(CGRect)frame
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupBarAppearanceLegacySupport.h b/LNPopupController/LNPopupController/Private/_LNPopupBarAppearanceLegacySupport.h
index ad4bc21..348e9a7 100644
--- a/LNPopupController/LNPopupController/Private/_LNPopupBarAppearanceLegacySupport.h
+++ b/LNPopupController/LNPopupController/Private/_LNPopupBarAppearanceLegacySupport.h
@@ -6,7 +6,7 @@
// Copyright © 2024 Leo Natan. All rights reserved.
//
-@import UIKit;
+#import
NS_ASSUME_NONNULL_BEGIN
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundMaskView.h b/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundMaskView.h
index dc13fe7..516dda0 100644
--- a/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundMaskView.h
+++ b/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundMaskView.h
@@ -2,11 +2,11 @@
// _LNPopupBarBackgroundMaskView.h
// LNPopupController
//
-// Created by Leo Natan on 27/09/2023.
-// Copyright © 2023 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2023-09-27.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
-@import UIKit;
+#import
NS_ASSUME_NONNULL_BEGIN
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundMaskView.m b/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundMaskView.m
index c3bfeeb..636160f 100644
--- a/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundMaskView.m
+++ b/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundMaskView.m
@@ -2,8 +2,8 @@
// _LNPopupBarBackgroundMaskView.m
// LNPopupController
//
-// Created by Leo Natan on 27/09/2023.
-// Copyright © 2023 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2023-09-27.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "_LNPopupBarBackgroundMaskView.h"
@@ -132,7 +132,6 @@ static CGGradientRef _LNGradientCreateWithEaseFunction(EASE_FUNC func, UIColor*
- (void)setWantsCutout:(BOOL)wantsCutout animated:(BOOL)animated
{
_wantsCutout = wantsCutout;
-// _wantsCutout = NO;
_targetAlpha = _wantsCutout ? 0.0 : 1.0;
if(animated == NO || self.superview.alpha == 0.0 || self.superview.isHidden)
@@ -183,7 +182,7 @@ static CGGradientRef _LNGradientCreateWithEaseFunction(EASE_FUNC func, UIColor*
CGContextSetBlendMode(ctx, kCGBlendModeDestinationIn);
[[UIColor.blackColor colorWithAlphaComponent:_currentAlpha] setFill];
- [[UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.floatingFrame, 1, 1) cornerRadius:self.floatingCornerRadius] fill];
+ [[UIBezierPath bezierPathWithRoundedRect:self.floatingFrame cornerRadius:self.floatingCornerRadius] fill];
UIGraphicsPopContext();
}
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundView.h b/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundView.h
index e3ec3b5..a701c39 100644
--- a/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundView.h
+++ b/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundView.h
@@ -2,8 +2,8 @@
// _LNPopupBarBackgroundView.h
// LNPopupController
//
-// Created by Leo Natan on 6/26/21.
-// Copyright © 2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-06-20.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundView.m b/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundView.m
index 9f08d30..226f149 100644
--- a/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundView.m
+++ b/LNPopupController/LNPopupController/Private/_LNPopupBarBackgroundView.m
@@ -2,8 +2,8 @@
// _LNPopupBarBackgroundView.m
// LNPopupController
//
-// Created by Leo Natan on 6/26/21.
-// Copyright © 2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2021-06-20.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "_LNPopupBarBackgroundView.h"
@@ -173,11 +173,14 @@
_cornerRadius = cornerRadius;
self.layer.cornerRadius = cornerRadius;
- _effectView.layer.cornerRadius = cornerRadius;
-
- if (@available(iOS 13.0, *))
+ if(@available(iOS 13.0, *))
{
self.layer.cornerCurve = kCACornerCurveContinuous;
+ }
+
+ _effectView.layer.cornerRadius = cornerRadius;
+ if(@available(iOS 13.0, *))
+ {
_effectView.layer.cornerCurve = kCACornerCurveContinuous;
}
}
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupBarShadowedImageView.h b/LNPopupController/LNPopupController/Private/_LNPopupBarShadowedImageView.h
deleted file mode 100644
index ae70dab..0000000
--- a/LNPopupController/LNPopupController/Private/_LNPopupBarShadowedImageView.h
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// _LNPopupBarShadowedImageView.h
-// LNPopupController
-//
-// Created by Leo Natan on 15/10/2023.
-// Copyright © 2023 Leo Natan. All rights reserved.
-//
-
-#import
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface _LNPopupBarShadowedImageView : UIImageView
-
-@property (nonatomic, assign) CGFloat cornerRadius;
-@property (nonatomic, copy) NSShadow* shadow;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupBarShadowedImageView.m b/LNPopupController/LNPopupController/Private/_LNPopupBarShadowedImageView.m
deleted file mode 100644
index 8d8620b..0000000
--- a/LNPopupController/LNPopupController/Private/_LNPopupBarShadowedImageView.m
+++ /dev/null
@@ -1,86 +0,0 @@
-//
-// _LNPopupBarShadowedImageView.m
-// LNPopupController
-//
-// Created by Leo Natan on 15/10/2023.
-// Copyright © 2023 Leo Natan. All rights reserved.
-//
-
-#import "_LNPopupBarShadowedImageView.h"
-#import "_LNPopupBackgroundShadowView.h"
-
-@implementation _LNPopupBarShadowedImageView
-{
- _LNPopupBackgroundShadowView* _shadowView;
-}
-
-- (instancetype)initWithFrame:(CGRect)frame
-{
- self = [super initWithFrame:frame];
-
- if(self)
- {
- _shadowView = [_LNPopupBackgroundShadowView new];
- }
-
- return self;
-}
-
-- (void)setShadow:(NSShadow *)shadow
-{
- _shadow = [shadow copy];
-
- _shadowView.shadow = shadow;
-}
-
-- (void)didMoveToSuperview
-{
- if(self.superview)
- {
- [self _updateShadowViewFrame];
- }
- else
- {
- [_shadowView removeFromSuperview];
- }
-}
-
-- (void)layoutSubviews
-{
- [super layoutSubviews];
- [self _updateShadowViewFrame];
-}
-
-- (void)setBounds:(CGRect)bounds
-{
- [super setBounds:bounds];
- [self _updateShadowViewFrame];
-}
-
-- (void)setCenter:(CGPoint)center
-{
- [super setCenter:center];
- [self _updateShadowViewFrame];
-}
-
-- (void)_updateShadowViewFrame
-{
- [self.superview insertSubview:_shadowView aboveSubview:self];
- _shadowView.frame = self.frame;
-}
-
-- (void)setCornerRadius:(CGFloat)cornerRadius
-{
- _cornerRadius = cornerRadius;
- self.layer.cornerRadius = cornerRadius;
- _shadowView.cornerRadius = cornerRadius;
-}
-
-- (void)setHidden:(BOOL)hidden
-{
- [super setHidden:hidden];
-
- _shadowView.hidden = hidden;
-}
-
-@end
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupBase64Utils.hh b/LNPopupController/LNPopupController/Private/_LNPopupBase64Utils.hh
new file mode 100644
index 0000000..13d2c1b
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupBase64Utils.hh
@@ -0,0 +1,75 @@
+//
+// _LNPopupBase64Utils.hh
+// LNPopupController
+//
+// Created by Léo Natan on 2024-09-01.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import
+#include
+#include
+
+namespace lnpopup {
+
+template
+struct base64_string : std::array {
+ consteval base64_string(const char (&input)[N]) : base64_string(input, std::make_index_sequence{}) {}
+ template
+ consteval base64_string(const char (&input)[N], std::index_sequence) : std::array{ input[Is]... } {}
+};
+
+template
+consteval const auto base64_encode(const char(&input)[N]) {
+ constexpr char encoding_table[] =
+ {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+ };
+
+ constexpr size_t out_len = 4 * (((N - 1) + 2) / 3) + 1;
+
+ size_t in_len = N - 1;
+ char output[out_len] {0};
+ size_t i = 0;
+ char *p = const_cast(output);
+
+ for(i = 0; in_len > 2 && i < in_len - 2; i += 3)
+ {
+ *p++ = encoding_table[(input[i] >> 2) & 0x3F];
+ *p++ = encoding_table[((input[i] & 0x3) << 4) | ((int)(input[i + 1] & 0xF0) >> 4)];
+ *p++ = encoding_table[((input[i + 1] & 0xF) << 2) | ((int)(input[i + 2] & 0xC0) >> 6)];
+ *p++ = encoding_table[input[i + 2] & 0x3F];
+ }
+
+ if(i < in_len)
+ {
+ *p++ = encoding_table[(input[i] >> 2) & 0x3F];
+ if(i == (in_len - 1))
+ {
+ *p++ = encoding_table[((input[i] & 0x3) << 4)];
+ *p++ = '=';
+ }
+ else
+ {
+ *p++ = encoding_table[((input[i] & 0x3) << 4) | ((int)(input[i + 1] & 0xF0) >> 4)];
+ *p++ = encoding_table[((input[i + 1] & 0xF) << 2)];
+ }
+ *p++ = '=';
+ }
+
+ return base64_string(output);
+}
+
+CF_INLINE
+auto decode_hidden_string(auto encoded)
+{
+ return [[NSString alloc] initWithData:[[NSData alloc] initWithBase64EncodedString:@(encoded.data()) options:0] encoding:NSUTF8StringEncoding];
+}
+
+} //namespace lnpopup
+
+#define LNPopupHiddenString(input) (lnpopup::decode_hidden_string(lnpopup::base64_encode("" input "")))
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupSwizzlingUtils.h b/LNPopupController/LNPopupController/Private/_LNPopupSwizzlingUtils.h
index ab59f8c..d9f5504 100644
--- a/LNPopupController/LNPopupController/Private/_LNPopupSwizzlingUtils.h
+++ b/LNPopupController/LNPopupController/Private/_LNPopupSwizzlingUtils.h
@@ -2,14 +2,16 @@
// _LNPopupSwizzlingUtils.h
// LNPopupController
//
-// Created by Leo Natan on 1/14/18.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2020-07-31.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import
-@import ObjectiveC;
+#import
#import
+CF_EXTERN_C_BEGIN
+
#define unavailable(...) @available(__VA_ARGS__)) { } else if(YES
#define LNSwizzleComplain(FORMAT, ...) \
@@ -20,11 +22,11 @@ raise(SIGTRAP); \
}
#ifndef LNAlwaysInline
-#define LNAlwaysInline inline __attribute__((__always_inline__))
+#define LNAlwaysInline CF_INLINE
#endif /* LNAlwaysInline */
LNAlwaysInline
-static BOOL LNSwizzleMethod(Class cls, SEL orig, SEL alt)
+BOOL LNSwizzleMethod(Class cls, SEL orig, SEL alt)
{
static BOOL shouldTrapAndPrint = NO;
static dispatch_once_t onceToken;
@@ -52,13 +54,13 @@ static BOOL LNSwizzleMethod(Class cls, SEL orig, SEL alt)
}
LNAlwaysInline
-static BOOL LNSwizzleClassMethod(Class cls, SEL orig, SEL alt)
+BOOL LNSwizzleClassMethod(Class cls, SEL orig, SEL alt)
{
return LNSwizzleMethod(object_getClass((id)cls), orig, alt);
}
LNAlwaysInline
-static void __LNCopyMethods(Class orig, Class target)
+void __LNCopyMethods(Class orig, Class target)
{
//Copy class methods
Class targetMetaclass = object_getClass(target);
@@ -91,7 +93,7 @@ static void __LNCopyMethods(Class orig, Class target)
}
LNAlwaysInline
-static BOOL LNDynamicallySubclass(id obj, Class target)
+BOOL LNDynamicallySubclass(id obj, Class target)
{
if(obj == nil)
{
@@ -131,7 +133,7 @@ static BOOL LNDynamicallySubclass(id obj, Class target)
}
LNAlwaysInline
-static Class LNDynamicSubclassSuper(id obj, Class dynamic)
+Class LNDynamicSubclassSuper(id obj, Class dynamic)
{
NSMutableDictionary* superRegistrar = objc_getAssociatedObject(obj, (void*)&objc_setAssociatedObject);
Class cls = superRegistrar[NSStringFromClass(dynamic)];
@@ -143,6 +145,6 @@ static Class LNDynamicSubclassSuper(id obj, Class dynamic)
return cls;
}
-
-NSString* _LNPopupDecodeBase64String(NSString* base64String);
NSArray* _LNPopupGetPropertyNames(Class cls, NSArray* excludedProperties);
+
+CF_EXTERN_C_END
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupSwizzlingUtils.m b/LNPopupController/LNPopupController/Private/_LNPopupSwizzlingUtils.m
index 3438589..ebe681d 100644
--- a/LNPopupController/LNPopupController/Private/_LNPopupSwizzlingUtils.m
+++ b/LNPopupController/LNPopupController/Private/_LNPopupSwizzlingUtils.m
@@ -2,18 +2,13 @@
// _LNPopupSwizzlingUtils.m
// LNPopupController
//
-// Created by Leo Natan on 1/14/18.
-// Copyright © 2015-2021 Leo Natan. All rights reserved.
+// Created by Léo Natan on 2018-01-15.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
//
#import "_LNPopupSwizzlingUtils.h"
@import ObjectiveC;
-NSString* _LNPopupDecodeBase64String(NSString* base64String)
-{
- return [[NSString alloc] initWithData:[[NSData alloc] initWithBase64EncodedString:base64String options:0] encoding:NSUTF8StringEncoding];
-}
-
NSArray* _LNPopupGetPropertyNames(Class cls, NSArray* excludedProperties)
{
unsigned int propertyCount = 0;
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupTransitionAnimator.h b/LNPopupController/LNPopupController/Private/_LNPopupTransitionAnimator.h
new file mode 100644
index 0000000..612c55d
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupTransitionAnimator.h
@@ -0,0 +1,56 @@
+//
+// _LNPopupTransitionAnimator.h
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import
+#import
+#import
+#import
+#import "_LNPopupTransitionView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface UIViewPropertyAnimator ()
+
+- (void)addAnimations:(void (^)(void))animation delayFactor:(CGFloat)delayFactor durationFactor:(CGFloat)durationFactor;
+
+@end
+
+@interface _LNPopupTransitionAnimator : NSObject
+
+- (instancetype)initWithTransitionView:(nullable _LNPopupTransitionView*)transitionView userView:(UIView*)view popupBar:(LNPopupBar*)popupBar popupContentView:(LNPopupContentView*)popupContentView;
+
+@property (nonatomic, strong, readonly) UIView* view;
+@property (nonatomic, strong, readonly) LNPopupBar* popupBar;
+@property (nonatomic, strong, readonly) LNPopupContentView* popupContentView;
+
+@property (nonatomic, strong, readonly, nullable) _LNPopupTransitionView* transitionView;
+@property (nonatomic, strong, readonly, nullable) UIView* crossfadeView;
+@property (nonatomic, readonly) CGRect sourceFrame;
+@property (nonatomic, readonly) CGRect targetFrame;
+@property (nonatomic, readonly) CGAffineTransform transform;
+
+@property (nonatomic, readonly) CGFloat scaledBarImageViewCornerRadius;
+@property (nonatomic, strong, readonly) NSShadow* scaledBarImageViewShadow;
+
+- (void)animateWithAnimator:(UIViewPropertyAnimator*)animator otherAnimations:(void(^)(void))otherAnimations NS_REQUIRES_SUPER;
+- (void)beforeAnyAnimation NS_REQUIRES_SUPER;
+- (void)performBeforeAdditionalAnimations NS_REQUIRES_SUPER;
+- (void)performAdditionalAnimations NS_REQUIRES_SUPER;
+- (void)performAdditionalDelayed015Animations NS_REQUIRES_SUPER;
+- (void)performAdditionalDelayed05Animations NS_REQUIRES_SUPER;
+- (void)performAdditional01Animations NS_REQUIRES_SUPER;
+- (void)performAdditional075Animations NS_REQUIRES_SUPER;
+- (void)performAdditional04Delayed015Animations NS_REQUIRES_SUPER;
+- (void)performAdditional075Delayed015Animations NS_REQUIRES_SUPER;
+- (void)performAdditionalCompletion NS_REQUIRES_SUPER;
+
+@property (nonatomic, readonly) LNPopupPresentationState targetState;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupTransitionAnimator.mm b/LNPopupController/LNPopupController/Private/_LNPopupTransitionAnimator.mm
new file mode 100644
index 0000000..5240639
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupTransitionAnimator.mm
@@ -0,0 +1,213 @@
+//
+// _LNPopupTransitionAnimatorOpen.mm
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import "_LNPopupTransitionAnimator.h"
+#import "LNPopupBar+Private.h"
+#import
+#import
+
+static const void* _LNPopupOpenCloseTransitionViewKey = &_LNPopupOpenCloseTransitionViewKey;
+
+@implementation _LNPopupTransitionAnimator
+
+- (instancetype)initWithTransitionView:(_LNPopupTransitionView *)transitionView userView:(UIView *)view popupBar:(LNPopupBar *)popupBar popupContentView:(LNPopupContentView *)popupContentView
+{
+ self = [super init];
+
+ if(self)
+ {
+ _transitionView = transitionView;
+ _view = view;
+ _popupBar = popupBar;
+ _popupContentView = popupContentView;
+ }
+
+ return self;
+}
+
+- (void)animateWithAnimator:(UIViewPropertyAnimator *)animator otherAnimations:(void (^)(void))otherAnimations
+{
+ static SEL transitionWillBegin = NSSelectorFromString(@"_transitionWillBeginToState:");
+ static SEL transitionDidEnd = NSSelectorFromString(@"_transitionDidEnd");
+
+ [UIView performWithoutAnimation:^{
+ [self.popupContentView layoutIfNeeded];
+ self.popupBar.imageView.alpha = 0.0;
+
+ if(self.transitionView == nil)
+ {
+ _transitionView = [[_LNPopupTransitionView alloc] initWithSourceView:self.view];
+ }
+
+ UIImage* image;
+ if(@available(iOS 13, *))
+ {
+ if(self.popupBar.swiftuiImageController != nil)
+ {
+ id contents = self.popupBar.swiftuiImageController.view.subviews.firstObject.layer.contents;
+ if(contents != nil && CFGetTypeID((__bridge CFTypeRef)contents) == CGImageGetTypeID())
+ {
+ image = [[UIImage alloc] initWithCGImage:(__bridge CGImageRef)contents];
+ }
+ else
+ {
+ image = [[[UIGraphicsImageRenderer alloc] initWithSize:self.popupBar.imageView.bounds.size] imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
+ [self.popupBar.imageView drawViewHierarchyInRect:self.popupBar.imageView.bounds afterScreenUpdates:NO];
+ }];
+ }
+ }
+ else
+ {
+ image = self.popupBar.imageView.image;
+ }
+ }
+ else
+ {
+ image = self.popupBar.imageView.image;
+ }
+
+ _crossfadeView = [[LNPopupImageView alloc] initWithImage:image];
+ _crossfadeView.contentMode = self.popupBar.imageView.contentMode;
+ _crossfadeView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ _crossfadeView.frame = _transitionView.bounds;
+ [_transitionView addSubview:_crossfadeView];
+
+ _transitionView.frame = self.sourceFrame;
+ [self beforeAnyAnimation];
+
+ objc_setAssociatedObject(self.transitionView.sourceView, _LNPopupOpenCloseTransitionViewKey, _transitionView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+ }];
+
+ [animator addAnimations:otherAnimations];
+
+ [animator addAnimations:^{
+ [UIView performWithoutAnimation:^{
+ [self.popupContentView.window addSubview:_transitionView];
+ [self performBeforeAdditionalAnimations];
+ }];
+
+ [self performAdditionalAnimations];
+
+ if([self.view respondsToSelector:transitionWillBegin])
+ {
+ NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:[self.view methodSignatureForSelector:transitionWillBegin]];
+ invocation.target = self.view;
+ LNPopupPresentationState targetState = self.targetState;
+ invocation.selector = transitionWillBegin;
+ [invocation setArgument:&targetState atIndex:2];
+ [invocation invoke];
+ }
+ }];
+
+ [animator addAnimations:^{
+ [UIView animateKeyframesWithDuration:0.0 delay:0.0 options:0 animations:^{
+ [UIView addKeyframeWithRelativeStartTime:0.15 relativeDuration:0.85 animations:^{
+ [self performAdditionalDelayed015Animations];
+ }];
+ } completion:nil];
+ }];
+
+ [animator addAnimations:^{
+ [UIView animateKeyframesWithDuration:0.0 delay:0.0 options:0 animations:^{
+ [UIView addKeyframeWithRelativeStartTime:0.15 relativeDuration:0.4 animations:^{
+ [self performAdditional04Delayed015Animations];
+ }];
+ } completion:nil];
+ }];
+
+ [animator addAnimations:^{
+ [self performAdditionalDelayed05Animations];
+ } delayFactor:0.5];
+
+ [animator addAnimations:^{
+ [UIView animateKeyframesWithDuration:0.0 delay:0.0 options:0 animations:^{
+ [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.75 animations:^{
+ [self performAdditional075Animations];
+ }];
+ } completion:nil];
+ } delayFactor:0.0];
+
+ [animator addAnimations:^{
+ [UIView animateKeyframesWithDuration:0.0 delay:0.0 options:0 animations:^{
+ [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.1 animations:^{
+ [self performAdditional01Animations];
+ }];
+ } completion:nil];
+ } delayFactor:0.0];
+
+ [animator addAnimations:^{
+ [UIView animateKeyframesWithDuration:0.0 delay:0.0 options:0 animations:^{
+ [UIView addKeyframeWithRelativeStartTime:0.15 relativeDuration:0.75 animations:^{
+ [self performAdditional075Delayed015Animations];
+ }];
+ } completion:nil];
+ } delayFactor:0.0];
+
+ [animator addCompletion:^(UIViewAnimatingPosition finalPosition) {
+ if([self.view respondsToSelector:transitionDidEnd])
+ {
+ [self.view performSelector:transitionDidEnd];
+ }
+
+ [self completeTransition];
+ }];
+}
+
+- (CGRect)sourceFrame
+{
+ return CGRectZero;
+}
+
+- (CGRect)targetFrame
+{
+ return CGRectZero;
+}
+
+- (CGAffineTransform)transform
+{
+ return CGAffineTransformIdentity;
+}
+
+- (CGFloat)scaledBarImageViewCornerRadius
+{
+ return 0.0;
+}
+
+- (NSShadow *)scaledBarImageViewShadow
+{
+ return nil;
+}
+
+- (LNPopupPresentationState)targetState
+{
+ return (LNPopupPresentationState)-1;
+}
+
+- (void)beforeAnyAnimation {}
+- (void)performBeforeAdditionalAnimations {}
+- (void)performAdditionalAnimations {}
+- (void)performAdditionalDelayed015Animations {}
+- (void)performAdditionalDelayed05Animations {}
+- (void)performAdditional01Animations {}
+- (void)performAdditional075Animations {}
+- (void)performAdditional04Delayed015Animations {}
+- (void)performAdditional075Delayed015Animations {}
+- (void)performAdditionalCompletion {}
+
+- (void)completeTransition
+{
+ [UIView performWithoutAnimation:^{
+ UIView* transitionView = objc_getAssociatedObject(self.transitionView.sourceView, _LNPopupOpenCloseTransitionViewKey);
+ [transitionView removeFromSuperview];
+ objc_setAssociatedObject(self.transitionView.sourceView, _LNPopupOpenCloseTransitionViewKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+ self.popupBar.imageView.alpha = 1.0;
+ [self performAdditionalCompletion];
+ }];
+}
+
+@end
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupTransitionCloseAnimator.h b/LNPopupController/LNPopupController/Private/_LNPopupTransitionCloseAnimator.h
new file mode 100644
index 0000000..5c56d61
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupTransitionCloseAnimator.h
@@ -0,0 +1,22 @@
+//
+// _LNPopupTransitionCloseAnimator.h
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import "_LNPopupTransitionAnimator.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface _LNPopupTransitionCloseAnimator : _LNPopupTransitionAnimator
+
+- (instancetype)initWithTransitionView:(nullable _LNPopupTransitionView*)transitionView userView:(UIView*)view popupBar:(LNPopupBar*)popupBar popupContentView:(LNPopupContentView*)popupContentView currentContentController:(UIViewController*)currentContentController containerController:(UIViewController*)containerController;
+
+@property (nonatomic, strong) UIViewController* currentContentController;
+@property (nonatomic, strong) UIViewController* containerController;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupTransitionCloseAnimator.m b/LNPopupController/LNPopupController/Private/_LNPopupTransitionCloseAnimator.m
new file mode 100644
index 0000000..1b97b38
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupTransitionCloseAnimator.m
@@ -0,0 +1,104 @@
+//
+// _LNPopupTransitionCloseAnimator.m
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import "_LNPopupTransitionCloseAnimator.h"
+#import "UIViewController+LNPopupSupportPrivate.h"
+#import "LNPopupBar+Private.h"
+
+@implementation _LNPopupTransitionCloseAnimator
+
+- (instancetype)initWithTransitionView:(_LNPopupTransitionView *)transitionView userView:(UIView *)view popupBar:(LNPopupBar *)popupBar popupContentView:(LNPopupContentView *)popupContentView currentContentController:(UIViewController *)currentContentController containerController:(UIViewController *)containerController
+{
+ self = [super initWithTransitionView:transitionView userView:view popupBar:popupBar popupContentView:popupContentView];
+
+ if(self)
+ {
+ self.currentContentController = currentContentController;
+ self.containerController = containerController;
+ }
+
+ return self;
+}
+
+- (CGRect)sourceFrame
+{
+ return [self.popupContentView.window convertRect:self.transitionView.sourceView.bounds fromView:self.transitionView.sourceView];
+}
+
+- (CGRect)targetFrame
+{
+ return [self.popupBar.imageView.window convertRect:self.popupBar.imageView.bounds fromView:self.popupBar.imageView];
+}
+
+- (CGFloat)scaledBarImageViewCornerRadius
+{
+ return MAX(self.popupBar.imageView.cornerRadius * self.sourceFrame.size.width / self.popupBar.imageView.bounds.size.width, self.popupBar.imageView.cornerRadius * self.sourceFrame.size.height / self.popupBar.imageView.bounds.size.height);
+}
+
+- (NSShadow *)scaledBarImageViewShadow
+{
+ NSShadow* scaled = self.popupBar.imageView.shadow.copy;
+ scaled.shadowBlurRadius = scaled.shadowBlurRadius * self.sourceFrame.size.width / self.popupBar.imageView.bounds.size.width;
+ return scaled;
+}
+
+- (LNPopupPresentationState)targetState
+{
+ return LNPopupPresentationStateBarPresented;
+}
+
+- (void)beforeAnyAnimation
+{
+ [super beforeAnyAnimation];
+
+ self.crossfadeView.alpha = 0.0;
+ self.crossfadeView.cornerRadius = self.transitionView.cornerRadius;
+}
+
+- (void)performAdditionalAnimations
+{
+ [super performAdditionalAnimations];
+
+ [self.transitionView setTargetFrameUpdatingTransform:self.targetFrame];
+
+ self.crossfadeView.cornerRadius = self.popupBar.imageView.cornerRadius;
+}
+
+- (void)performAdditionalDelayed015Animations
+{
+ [super performAdditionalDelayed015Animations];
+
+ if(self.containerController._ln_shouldPopupContentAnyFadeForTransition)
+ {
+ if(self.containerController._ln_shouldPopupContentViewFadeForTransition)
+ {
+ self.popupContentView.alpha = 0.0;
+ }
+ else
+ {
+ self.currentContentController.view.alpha = 0.0;
+ }
+ }
+}
+
+- (void)performAdditional04Delayed015Animations
+{
+ [super performAdditional04Delayed015Animations];
+
+ self.crossfadeView.alpha = 1.0;
+}
+
+- (void)performAdditionalCompletion
+{
+ [super performAdditionalCompletion];
+
+ self.popupContentView.alpha = 1.0;
+ self.currentContentController.view.alpha = 1.0;
+}
+
+@end
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericCloseAnimator.h b/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericCloseAnimator.h
new file mode 100644
index 0000000..46e3ae6
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericCloseAnimator.h
@@ -0,0 +1,17 @@
+//
+// _LNPopupTransitionGenericCloseAnimator.h
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import "_LNPopupTransitionCloseAnimator.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface _LNPopupTransitionGenericCloseAnimator : _LNPopupTransitionCloseAnimator
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericCloseAnimator.m b/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericCloseAnimator.m
new file mode 100644
index 0000000..614418f
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericCloseAnimator.m
@@ -0,0 +1,45 @@
+//
+// _LNPopupTransitionGenericCloseAnimator.m
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import "_LNPopupTransitionGenericCloseAnimator.h"
+
+@implementation _LNPopupTransitionGenericCloseAnimator
+{
+ NSShadow* _targetShadow;
+}
+
+- (void)animateWithAnimator:(UIViewPropertyAnimator *)animator otherAnimations:(void (^)(void))otherAnimations
+{
+ [super animateWithAnimator:animator otherAnimations:otherAnimations];
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((animator.duration * 0.38) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ [animator addAnimations:^{
+ self.transitionView.cornerRadius = self.popupBar.imageView.cornerRadius;
+ }];
+ });
+}
+
+- (void)beforeAnyAnimation
+{
+ [super beforeAnyAnimation];
+
+ _targetShadow = self.popupBar.imageView.shadow.copy;
+
+ NSShadow* hiddenShadow = [_targetShadow copy];
+ hiddenShadow.shadowColor = [_targetShadow.shadowColor colorWithAlphaComponent:0.0];
+ self.transitionView.shadow = hiddenShadow;
+}
+
+- (void)performAdditionalAnimations
+{
+ [super performAdditionalAnimations];
+
+ self.transitionView.shadow = _targetShadow;
+}
+
+@end
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericOpenAnimator.h b/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericOpenAnimator.h
new file mode 100644
index 0000000..d13eb03
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericOpenAnimator.h
@@ -0,0 +1,17 @@
+//
+// _LNPopupTransitionGenericOpenAnimator.h
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import "_LNPopupTransitionOpenAnimator.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface _LNPopupTransitionGenericOpenAnimator : _LNPopupTransitionOpenAnimator
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericOpenAnimator.m b/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericOpenAnimator.m
new file mode 100644
index 0000000..f7ae2bb
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupTransitionGenericOpenAnimator.m
@@ -0,0 +1,35 @@
+//
+// _LNPopupTransitionGenericOpenAnimator.m
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import "_LNPopupTransitionGenericOpenAnimator.h"
+
+@implementation _LNPopupTransitionGenericOpenAnimator
+{
+ NSShadow* _targetShadow;
+}
+
+- (void)beforeAnyAnimation
+{
+ [super beforeAnyAnimation];
+
+ self.transitionView.shadow = self.popupBar.imageView.shadow.copy;
+ self.transitionView.cornerRadius = self.scaledBarImageViewCornerRadius;
+
+ _targetShadow = [self.transitionView.shadow copy];
+ _targetShadow.shadowColor = [_targetShadow.shadowColor colorWithAlphaComponent:0.0];
+}
+
+- (void)performAdditionalAnimations
+{
+ [super performAdditionalAnimations];
+
+ self.transitionView.shadow = _targetShadow;
+ self.transitionView.cornerRadius = 0.0;
+}
+
+@end
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupTransitionOpenAnimator.h b/LNPopupController/LNPopupController/Private/_LNPopupTransitionOpenAnimator.h
new file mode 100644
index 0000000..b714777
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupTransitionOpenAnimator.h
@@ -0,0 +1,21 @@
+//
+// _LNPopupTransitionOpenAnimator.h
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import
+#import "_LNPopupTransitionAnimator.h"
+#import
+#import
+#import "_LNPopupTransitionView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface _LNPopupTransitionOpenAnimator : _LNPopupTransitionAnimator
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/LNPopupController/LNPopupController/Private/_LNPopupTransitionOpenAnimator.m b/LNPopupController/LNPopupController/Private/_LNPopupTransitionOpenAnimator.m
new file mode 100644
index 0000000..027cb48
--- /dev/null
+++ b/LNPopupController/LNPopupController/Private/_LNPopupTransitionOpenAnimator.m
@@ -0,0 +1,79 @@
+//
+// _LNPopupTransitionOpenAnimator.m
+// LNPopupController
+//
+// Created by Léo Natan on 2025-03-24.
+// Copyright © 2015-2025 Léo Natan. All rights reserved.
+//
+
+#import "_LNPopupTransitionOpenAnimator.h"
+#import