I create a UIKit PullDown menu (in a positionMenu button), with the following code:
func setupPullDownMenu() {
let menu1 = UIAction(title: "Menu1") { [self] _ in
// some code
}
let menu2 = UIAction(title: "Menu2") { [self] _ in
// some code
}
let menu3 = UIAction(title: "Menu3") { [self] _ in
// some code
}
let menu = UIMenu(title: "Positions", children: [menu1, menu2, menu3])
positionMenu.menu = menu
positionMenu.showsMenuAsPrimaryAction = true
}
In Portrait, options are listed as Menu1, Menu2, Menu3
But in Landscape it is the reverse: Menu3, Menu2, Menu1
I use Xcode 16.1ß and iOS 17.0 simulator
Is it the expected behaviour ?
UIKit
RSS for tagConstruct and manage graphical, event-driven user interfaces for iOS or tvOS apps using UIKit.
Posts under UIKit tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
We appear to be experiencing a bug with the latest beta for visionOS, we are attempting to playback a video with a transparent background in the app. In the previous beta playback worked as expected and the transparent parts of the video were transparent. In the latest beta the background appears black. The view we are using in a SwiftUI wrapped version of AVPlayerViewController, we have narrowed the bug down to only occurring only when playback is being presented in the embedded experience mode, if playback is being done in the expanded experience then playback is as expected.
This has only only been visible on an actual device, we have been unable to replicate the behaviour in the simulator using the latest Xcode 16.0 beta(beta 5 (16A5221g))
This is sample project that shows off the bug
My app exports a custom file type identifier for a file that it exports to share between other users of the same app. However in the list of apps in UIActivityViewController view, my app is listed far off screen. How can I make my app which owns the file type appear first in the list, instead of seeing many irrelevant apps that can't actually open my file?
Plist snippets:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>My Custom App File</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>abc.myapp.myextension</string>
</array>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.content</string>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>My Custom App File</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>abc.myapp.myextension</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>myextension</string>
</array>
<key>public.mime-type</key>
<array>
<string>application/octet-stream</string>
</array>
</dict>
</dict>
</array>
I've noticed the tab bar in tvOS 18 (beta) is positioned lower on the TV screen than in previous versions. Bug? I see no documentation on this important UI change...
If this is not a bug, is there any way to adjust the y coordinate of the tab bar location in tvOS 18? I would really like to restore this to the previous location for my app and avoid having to do OS-conditional constraints for all my views/pages.
I am writing to report an issue I encountered with iOS 18 beta that affects my application, which has been available on the App Store for over two years and currently has over 60,000 active users.
My application utilizes a UITabBarController to manage multiple tabs, where each tab hosts a UIViewController embedded within a UINavigationController. The application operates in two different states, where users may have either 5, 4, or 3 tabBarItems depending on their configuration.
The issue arises when fewer than 5 tabs are present. In these cases, I add child view controllers to the UITabBarController to ensure they are displayed above the tab bar, rather than below it. The relevant code snippet is as follows:
tabBarController.addChild(childController)
tabBarController.view.addSubview(childController.view)
Prior to iOS 18, this implementation functioned as expected. However, with the release of iOS 18, adding a child view controller to the UITabBarController results in the child being incorrectly added as a UITabBarItem. This misbehavior leads to an application crash when the unintended tab is selected.
The crash trace is as follows:
"Inconsistency in UITabBar items and view controllers detected. No view controller matches the UITabBarItem '<UITabBarItem: 0x142d9c480> selected'."
I have attached screenshots from iOS 18 and previous versions to illustrate the issue, which compares the expected behavior in earlier iOS versions with the problematic behavior in iOS 18.
I appreciate your attention to this matter and look forward to any guidance or resolution you can provide.
Xcode Issue
Hello all,
I have encountered an error in the recent Xcode versions 15.4 and the version before that. When creating an iMessage App from the template menu and running the project out of the box. The simulator will fail to add the app and return and error:
Failed to get pid info for pid 0: Operation not permitted
This is something not local to me, don’t believe me try it out for yourself. The app will not work. I have looked endlessly online and have found nothing. If you see any posts on forums about this issue it is me.
However I have noticed Apple’s IceCreamBuilder project works some how.
https://developer.apple.com/documentation/messages/icecreambuilder-building-an-imessage-extension
Any help or advice is appreciated thank you all.
On testing my app with tvOS 18, I have noticed the Siri Remote back button no longer provides system-provided behavior when interacting with tab bar controller pages. Instead of moving focus back to the tab bar when pressed, the back button will close the app, as if the Home button was pressed. This occurs both on device and in the Simulator.
Create tvOS project with a tab bar controller.
Create pages/tabs which contain focusable items (ie. buttons)
Scroll down to any focusable item (ie. a button or UICollectionView cell)
Hit the Siri Remote back button. See expect behavior below:
Expected behavior: System-provided behavior should move focus back to the tab bar at the top of the screen.
Actual results: App is closed and user is taken back to the Home Screen.
Has anyone else noticed this behavior?
Platform and Version
iOS
Development environment: Xcode 16.0 beta 5 (16A5221g), macOS 14.6.1 (23G93)
Run-time configuration: iOS 18.0 beta 5 (22A5326g)
Description of Problem
Starting with iOS 18 SDK, test bundles containing a +load method that accesses UIScreen.mainScreen result in deadlock during test bundle injection.
Also filed as FB14703057
I'm looking for clarity on whether this behavior is considered a bug in iOS or whether we will need to change the implementation of our app.
Steps to Reproduce
Create a new iOS app project using Objective-C and including unit tests
Add the following code snippet to any .m file in the test target:
@interface Foo: NSObject
@end
@implementation Foo
+ (void)load {
UIScreen * const mainScreen = UIScreen.mainScreen;
NSLog(@"%@", mainScreen);
}
@end
Run the tests
Expected Behavior
As with iOS 17 & Xcode 15, the tests run to completion.
Actual Behavior
With iOS 18 & Xcode 16, deadlock during test bundle injection.
stack_trace.txt
I am developing SDK and swizzling viewDidAppear.
I have a customer who implements a custom TabBar Navigation where VC's are added to the hierarchy on the first load, and then, he changes the opacity to the currently displayed tab so the next time the user sees the tab - viewDidAppear isn't called, so my code isn't called.
I'm attaching a sample project which reproduces that.
Is there any way to trigger ViewDidAppear intentionally?
If yes, what can be the side effect of doing that?
Do I have any other alternative in this case?
@main
struct DemoCustomTabViewApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
TabBarRouterView()
}
}
}
import UIKit
// MARK: - TabBarItem (unchanged)
enum TabBarItem: Identifiable, CaseIterable {
case home, search, profile
var id: Self { self }
var title: String {
switch self {
case .home: return "Home"
case .search: return "Search"
case .profile: return "Profile"
}
}
var icon: String {
switch self {
case .home: return "house"
case .search: return "magnifyingglass"
case .profile: return "person"
}
}
}
// MARK: - NavigationControllerView
struct NavigationControllerView: UIViewControllerRepresentable {
var rootViewController: UIViewController
func makeUIViewController(context: Context) -> UINavigationController {
let navigationController = UINavigationController(rootViewController: rootViewController)
return navigationController
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
}
// MARK: - TabBarRouterViewModel
class TabBarRouterViewModel: ObservableObject {
@Published var currentTab: TabBarItem = .home
@Published var cachedViews: [TabBarItem: AnyView] = [:]
let tabs: [TabBarItem] = TabBarItem.allCases
func switchTab(to tab: TabBarItem) {
currentTab = tab
}
func createView(for tab: TabBarItem) -> AnyView {
if let cachedView = cachedViews[tab] {
return cachedView
}
let rootViewController: UIViewController
switch tab {
case .home:
rootViewController = UIHostingController(rootView: Text("Home View"))
case .search:
rootViewController = UIHostingController(rootView: Text("Search View"))
case .profile:
rootViewController = UIHostingController(rootView: Text("Profile View"))
}
let navigationView = NavigationControllerView(rootViewController: rootViewController)
let anyView = AnyView(navigationView)
cachedViews[tab] = anyView
return anyView
}
}
// MARK: - CustomTabBarView (unchanged)
struct CustomTabBarView: View {
let tabs: [TabBarItem]
@Binding var selectedTab: TabBarItem
let onTap: (TabBarItem) -> Void
var body: some View {
HStack {
ForEach(tabs) { tab in
Spacer()
VStack {
Image(systemName: tab.icon)
.font(.system(size: 24))
Text(tab.title)
.font(.caption)
}
.foregroundColor(selectedTab == tab ? .blue : .gray)
.onTapGesture {
onTap(tab)
}
Spacer()
}
}
.frame(height: 60)
.background(Color.white)
.shadow(radius: 2)
}
}
// MARK: - TabBarRouterView
struct TabBarRouterView: View {
@StateObject private var viewModel = TabBarRouterViewModel()
var body: some View {
VStack(spacing: .zero) {
contentView
CustomTabBarView(
tabs: viewModel.tabs,
selectedTab: $viewModel.currentTab,
onTap: viewModel.switchTab
)
}
.edgesIgnoringSafeArea(.bottom)
}
private var contentView: some View {
ZStack {
ForEach(viewModel.tabs) { tab in
viewModel.createView(for: tab)
.opacity(viewModel.currentTab == tab ? 1.0 : 0.0)
}
}
}
}
I made a custom slider by subclassing UISlider, and I'm trying to add scrubbing functionality to it, but for some reason the scrubbing is barely even noticeable at 0.1? In my code, I tried multiplying change in x distance by the scrubbing value, but it doesn't seem to work. Also, when I manually set the scrubbing speed to a lower value such as 0.01, it does go slower but it looks really laggy and weird?? What am I doing wrong?
Any help or advice would be greatly appreciated!
Subclass of UISlider:
class SizeSliderView: UISlider {
private var previousLocation: CGPoint?
private var currentLocation: CGPoint?
private var translation: CGFloat = 0
private var scrubbingSpeed: CGFloat = 1
private var defaultDiameter: Float
init(startValue: Float = 0, defaultDiameter: Float = 500) {
self.defaultDiameter = defaultDiameter
super.init(frame: .zero)
value = clamp(value: startValue, min: minimumValue, max: maximumValue)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
clear()
createThumbImageView()
addTarget(self, action: #selector(valueChanged(_:)), for: .valueChanged)
}
// Clear elements
private func clear() {
tintColor = .clear
maximumTrackTintColor = .clear
backgroundColor = .clear
thumbTintColor = .clear
}
// Call when value is changed
@objc private func valueChanged(_ sender: SizeSliderView) {
CATransaction.begin()
CATransaction.setDisableActions(true)
CATransaction.commit()
createThumbImageView()
}
// Create thumb image with thumb diameter dependent on thumb value
private func createThumbImageView() {
let thumbDiameter = CGFloat(defaultDiameter * value)
let thumbImage = UIColor.red.circle(CGSize(width: thumbDiameter, height: thumbDiameter))
setThumbImage(thumbImage, for: .normal)
setThumbImage(thumbImage, for: .highlighted)
setThumbImage(thumbImage, for: .application)
setThumbImage(thumbImage, for: .disabled)
setThumbImage(thumbImage, for: .focused)
setThumbImage(thumbImage, for: .reserved)
setThumbImage(thumbImage, for: .selected)
}
// Return true so touches are tracked
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self)
// Ensure that start location is on thumb
let thumbDiameter = CGFloat(defaultDiameter * value)
if location.x < bounds.width / 2 - thumbDiameter / 2 || location.x > bounds.width / 2 + thumbDiameter / 2 || location.y < 0 || location.y > thumbDiameter {
return false
}
previousLocation = location
super.beginTracking(touch, with: event)
return true
}
// Track based on moving slider
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
guard isTracking else { return false }
guard let previousLocation = previousLocation else { return false }
// Reference
// location: location of touch relative to device
// delta location: change in touch location WITH scrubbing
// adjusted location: location of touch to slider bounds (WITH scrubbing)
// translation: location of slider relative to device
let location = touch.location(in: self)
currentLocation = location
scrubbingSpeed = getScrubbingSpeed(for: location.y - 50)
let deltaLocation = (location.x - previousLocation.x) * scrubbingSpeed
var adjustedLocation = deltaLocation + previousLocation.x - translation
if adjustedLocation < 0 {
translation += adjustedLocation
adjustedLocation = deltaLocation + previousLocation.x - translation
} else if adjustedLocation > bounds.width {
translation += adjustedLocation - bounds.width
adjustedLocation = deltaLocation + previousLocation.x - translation
}
self.previousLocation = CGPoint(x: deltaLocation + previousLocation.x, y: location.y)
let newValue = Float(adjustedLocation / bounds.width) * (maximumValue - minimumValue) + minimumValue
setValue(newValue, animated: false)
sendActions(for: .valueChanged)
return true
}
// Reset start and current location
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.currentLocation = nil
self.translation = 0
super.touchesEnded(touches, with: event)
}
// Thumb location follows current location and resets in middle
override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect {
let thumbDiameter = CGFloat(defaultDiameter * value)
let origin = CGPoint(x: (currentLocation?.x ?? bounds.width / 2) - thumbDiameter / 2, y: (currentLocation?.y ?? thumbDiameter / 2) - thumbDiameter / 2)
return CGRect(origin: origin, size: CGSize(width: thumbDiameter, height: thumbDiameter))
}
private func getScrubbingSpeed(for value: CGFloat) -> CGFloat {
switch value {
case 0:
return 1
case 0...50:
return 0.5
case 50...100:
return 0.25
case 100...:
return 0.1
default:
return 1
}
}
private func clamp(value: Float, min: Float, max: Float) -> Float {
if value < min {
return min
} else if value > max {
return max
} else {
return value
}
}
}
UIView representative:
struct SizeSlider: UIViewRepresentable {
private var startValue: Float
private var defaultDiameter: Float
init(startValue: Float, defaultDiameter: Float) {
self.startValue = startValue
self.defaultDiameter = defaultDiameter
}
func makeUIView(context: Context) -> SizeSliderView {
let view = SizeSliderView(startValue: startValue, defaultDiameter: defaultDiameter)
view.minimumValue = 0.1
view.maximumValue = 1
return view
}
func updateUIView(_ uiView: SizeSliderView, context: Context) {}
}
Content view:
struct ContentView: View {
var body: some View {
SizeSlider(startValue: 0.20, defaultDiameter: 100)
.frame(width: 400)
}
}
Tab bars on iPadOS 18 have moved to the top of the screen. They now share space with navigation bars.
We have added calls to setTabBarHidden(_:animated:) alongside existing calls to setNavigationBarHidden(_:animated:) in pushed view controller's viewWillAppear(_:) methods to manage the appearance of the tab bar and navigation bar within navigation controllers.
This results in layout issues with the safe area and navigation bar. I've attached screenshots from an example app demonstrating the issue. How can we manage the appearance of both the navigation bar and tab bar so that they share the same space when visible, but are properly hidden and excluded from the safe area when not?
/// The root view controller shows both the navigation bar and tab bar
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
tabBarController?.setTabBarHidden(false, animated: animated)
}
}
/// The second view controller hides both the navigation bar and tab bar
class ViewController2: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
tabBarController?.setTabBarHidden(true, animated: animated)
}
@IBAction func customBackButtonTapped(_ sender: Any) {
navigationController?.popViewController(animated: true)
}
}
/// The third view controller shows the navigation bar but hides the tab bar
class ViewController3: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
tabBarController?.setTabBarHidden(true, animated: animated)
}
}
Hi
We're using CGPDFDocument to retrieve the PDF document and draw it in the UIView using CGContext.DrawPDFPage. While doing this operation, I was able to get an NSMallocException with a specific document.
I was able to get this exception only on real devices; emulators are working fine. The device used is the iPad (5th generation), which has iOS 16.7.8.
Steps to replicate:
Open the application at the link (we have attached the sample with PDF file with the issue in the link) https://www.dropbox.com/scl/fi/mosvcnqc0nvxwdjf8k0m5/SimplePDFVieweriOS.zip?rlkey=0ntj75yjg71kjwtwwe06uax5v&st=mmv08e2f&dl=0
Navigate through the pages using the next button.
After the page navigation to page 7, and the application will throw an NSMallocException with the reason: Failed to grow buffer
Platform: Xamarin.iOS
Development tool: Visual Studio 2022
Stack trace for the issue:
Native stack trace:
0 CoreFoundation 0x00000001b32e9418 55B9BA28-4C5C-3FE7-9C47-4983337D6E83 + 37912
1 libobjc.A.dylib 0x00000001ac63dc28 objc_exception_throw + 56
2 CoreFoundation 0x00000001b347c5c8 55B9BA28-4C5C-3FE7-9C47-4983337D6E83 + 1689032
3 CoreFoundation 0x00000001b3474504 55B9BA28-4C5C-3FE7-9C47-4983337D6E83 + 1656068
4 CoreFoundation 0x00000001b3363160 __CFSafelyReallocate + 68
5 CoreFoundation 0x00000001b33aae9c 55B9BA28-4C5C-3FE7-9C47-4983337D6E83 + 831132
6 CoreFoundation 0x00000001b335af2c CFDataSetLength + 152
7 CoreGraphics 0x00000001b4ca71a4 CGDataProviderCopyData + 240
8 ImageIO 0x00000001b7fafd38 7E9A543E-EE3C-34B7-9EFE-D5F6357F2FA3 + 32056
9 ImageIO 0x00000001b7fb1124 7E9A543E-EE3C-34B7-9EFE-D5F6357F2FA3 + 37156
10 ImageIO 0x00000001b7ff7124 7E9A543E-EE3C-34B7-9EFE-D5F6357F2FA3 + 323876
11 ImageIO 0x00000001b7fb0fe0 CGImageSourceCreateWithDataProvider + 176
12 CoreGraphics 0x00000001b4d7ac0c EDFE1CE3-C3C4-30A2-AC92-099D20347781 + 1018892
13 CoreGraphics 0x00000001b4d7a9a0 CGPDFImageCreateImage + 208
14 CoreGraphics 0x00000001b4decbc8 EDFE1CE3-C3C4-30A2-AC92-099D20347781 + 1485768
15 CoreGraphics 0x00000001b4d26e34 EDFE1CE3-C3C4-30A2-AC92-099D20347781 + 675380
16 CoreGraphics 0x00000001b4d2f7c4 EDFE1CE3-C3C4-30A2-AC92-099D20347781 + 710596
17 CoreGraphics 0x00000001b4ce16dc CGPDFScannerScan + 436
18 CoreGraphics 0x00000001b5073198 CGPDFDrawingContextDraw + 112
19 CoreGraphics 0x00000001b4d271b0 EDFE1CE3-C3C4-30A2-AC92-099D20347781 + 676272
20 CoreGraphics 0x00000001b4d2f7c4 EDFE1CE3-C3C4-30A2-AC92-099D20347781 + 710596
21 CoreGraphics 0x00000001b4ce16dc CGPDFScannerScan + 436
22 CoreGraphics 0x00000001b4d3f68c CGContextDrawPDFPageWithDrawingCallbacks + 2680
23 CoreGraphics 0x00000001b4cdb04c CGContextDrawPDFPage + 32
24 SimplePDFViewer 0x0000000100f0cb90 SimplePDFViewer + 1969040
25 SimplePDFViewer 0x0000000100eeb3c0 SimplePDFViewer + 1831872
26 SimplePDFViewer 0x0000000100d35b14 SimplePDFViewer + 39700
27 SimplePDFViewer 0x0000000100e8eb80 SimplePDFViewer + 1452928
28 SimplePDFViewer 0x0000000100f483f4 mono_pmip + 25092
29 SimplePDFViewer 0x0000000100fd8b90 mono_pmip + 616864
30 SimplePDFViewer 0x0000000100fdc014 mono_pmip + 630308
31 SimplePDFViewer 0x0000000100d32450 SimplePDFViewer + 25680
32 SimplePDFViewer 0x0000000100d32328 SimplePDFViewer + 25384
33 UIKitCore 0x00000001b5412150 27A9C298-B702-3C39-8C06-07196E4CD16B + 1667408
34 QuartzCore 0x00000001b47990e8 9E40B2EB-260C-3AF1-AE45-A1A551A8C1B9 + 119016
35 QuartzCore 0x00000001b480adb8 9E40B2EB-260C-3AF1-AE45-A1A551A8C1B9 + 585144
36 QuartzCore 0x00000001b47987a8 9E40B2EB-260C-3AF1-AE45-A1A551A8C1B9 + 116648
37 QuartzCore 0x00000001b4798034 9E40B2EB-260C-3AF1-AE45-A1A551A8C1B9 + 114740
38 QuartzCore 0x00000001b47a89f0 9E40B2EB-260C-3AF1-AE45-A1A551A8C1B9 + 182768
39 QuartzCore 0x00000001b47d5dc0 9E40B2EB-260C-3AF1-AE45-A1A551A8C1B9 + 368064
40 QuartzCore 0x00000001b47c0dc8 9E40B2EB-260C-3AF1-AE45-A1A551A8C1B9 + 282056
41 CoreFoundation 0x00000001b33698e8 55B9BA28-4C5C-3FE7-9C47-4983337D6E83 + 563432
42 CoreFoundation 0x00000001b32f951c 55B9BA28-4C5C-3FE7-9C47-4983337D6E83 + 103708
43 CoreFoundation 0x00000001b3355214 55B9BA28-4C5C-3FE7-9C47-4983337D6E83 + 479764
44 CoreFoundation 0x00000001b3359d20 CFRunLoopRunSpecific + 584
45 GraphicsServices 0x00000001eab69998 GSEventRunModal + 160
46 UIKitCore 0x00000001b55ec448 27A9C298-B702-3C39-8C06-07196E4CD16B + 3609672
47 UIKitCore 0x00000001b55ec0c0 UIApplicationMain + 312
48 SimplePDFViewer 0x000000010106b350 xamarin_UIApplicationMain + 60
49 SimplePDFViewer 0x0000000100f0de18 SimplePDFViewer + 1973784
50 SimplePDFViewer 0x0000000100eecd4c SimplePDFViewer + 1838412
51 SimplePDFViewer 0x0000000100eece70 SimplePDFViewer + 1838704
52 SimplePDFViewer 0x0000000100d33ad0 SimplePDFViewer + 31440
53 SimplePDFViewer 0x0000000100e8eb80 SimplePDFViewer + 1452928
54 SimplePDFViewer 0x0000000100f483f4 mono_pmip + 25092
55 SimplePDFViewer 0x0000000100fd8b90 mono_pmip + 616864
56 SimplePDFViewer 0x0000000100fddbdc mono_pmip + 637420
57 SimplePDFViewer 0x0000000100f2d5ec SimplePDFViewer + 2102764
58 SimplePDFViewer 0x00000001010802dc xamarin_log + 22464
59 SimplePDFViewer 0x0000000100d334bc SimplePDFViewer + 29884
60 dyld 0x00000001d0b04344 8A4B89B7-D348-375B-97B1-FC8A84E3E5CE + 82756
Kindly let us know if you need any further details regarding this issue.
I'm trying to convert a view (ScrollView) to PDF, on my physical device iPhone 7 with iOS 15.8 it works perfectly, but on my friends' iPhone 11 and iPhone 13 both with iOS 17+ the application crashes at the time of conversion, it also crashes in the xCode preview and in the Simulators without showing errors.
I call the function like this:
Button(action: {
if included && !saving {
if signatureImage != nil {
exportPDF(name: "ToR-(patient.name)-(patient.id)") {
self
} completion: { status, url in
if let url = url, status {
self.PDFUrl = url
self.showActionSheet.toggle()
print("URL => (url) - Status (status)")
} else {
print("Failed")
}
}
} else {
self.scrollToBottom = true
}
}
}) {
if saving {
ProgressView()
.tint(Color("MainColor"))
.scaleEffect(1.5)
} else {
Text(NSLocalizedString("Save", comment: ""))
}
}
And the function is this:
import SwiftUI
import UIKit
extension View {
func convertToScrollView<Content: View>(@ViewBuilder content: @escaping () -> Content) -> UIScrollView {
let scrollView = UIScrollView()
let hostingController = UIHostingController(rootView: content()).view!
hostingController.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
hostingController.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
hostingController.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
hostingController.topAnchor.constraint(equalTo: scrollView.topAnchor),
hostingController.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
hostingController.widthAnchor.constraint(equalToConstant: screenBounds().width)
]
scrollView.addSubview(hostingController)
scrollView.addConstraints(constraints)
scrollView.layoutIfNeeded()
return scrollView
}
func exportPDF<Content: View>(name: String, @ViewBuilder content: @escaping () -> Content, completion: @escaping (Bool, URL?) -> ()) {
let documentDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
let outputFileURL = documentDirectory.appendingPathComponent("\(name).pdf")
let pdfView = convertToScrollView {
content()
}
pdfView.tag = 1009
let size = pdfView.contentSize
pdfView.frame = CGRect(x: 0, y: getSafeArea().top, width: size.width, height: size.height)
getRootController().view.insertSubview(pdfView, at: 0)
let renderer = UIGraphicsPDFRenderer(bounds: CGRect(x: 0, y: 0, width: size.width, height: size.height))
do {
try renderer.writePDF(to: outputFileURL, withActions: { context in
context.beginPage()
pdfView.layer.render(in: context.cgContext)
})
completion(true, outputFileURL)
}
catch {
completion(false, nil)
print(error.localizedDescription)
}
getRootController().view.subviews.forEach { view in
if view.tag == 1009 {
view.removeFromSuperview()
}
}
//pdfView.removeFromSuperview()
}
func screenBounds() -> CGRect {
return UIScreen.main.bounds
}
func getRootController() -> UIViewController {
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return .init()
}
guard let root = screen.windows.first?.rootViewController else {
return .init()
}
return root
}
func getSafeArea() -> UIEdgeInsets {
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return .zero
}
guard let safeArea = screen.windows.first?.safeAreaInsets else {
return .zero
}
return safeArea
}
}
In one of our SwiftUI projects, we intensively use UIViewController to present a SwiftUI View modally, and it works perfectly under normal circumstances.
However, we have observed that when screen mirroring is enabled on the iPhone, the @Environment viewControllerHolder becomes nil, preventing the proper presentation of another view. Xcode (14.5) does not flag any issues with the code, and our project is set to build for iOS 17.5.
Without changing too many codebase, is there a way to fix this unexpected issue?
import SwiftUI
import UIKit
struct ContentView: View {
@Environment(\.viewController) private var viewControllerHolder: UIViewController?
@State var presentSecondPage = false
var body: some View {
VStack(spacing: 40) {
Text("This is First Page")
Button("Present Second Page") {
presentSecondPage = true
}
}
.onChange(of: presentSecondPage) {
if presentSecondPage {
viewControllerHolder?.present(style: .fullScreen) {
SecondPage(presentSecondPage: $presentSecondPage)
}
}
}
}
}
struct SecondPage: View {
@Environment(\.viewController) private var viewControllerHolder: UIViewController?
@Binding var presentSecondPage: Bool
var body: some View {
VStack(spacing: 40) {
Text("This is Second Page")
Button("Back to First Page") {
presentSecondPage = false
viewControllerHolder?.dismiss(animated: true)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
}
}
struct ViewControllerHolder {
weak var value: UIViewController?
}
struct ViewControllerKey: EnvironmentKey {
static var defaultValue: ViewControllerHolder {
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
let rootVC = windowScene?.windows.first(where: { $0.isKeyWindow })?.rootViewController
return ViewControllerHolder(value: rootVC)
}
}
extension EnvironmentValues {
var viewController: UIViewController? {
get { return self[ViewControllerKey.self].value }
set { self[ViewControllerKey.self].value = newValue }
}
}
extension UIViewController {
func present<Content: View>(style: UIModalPresentationStyle = .automatic, @ViewBuilder builder: () -> Content) {
let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
toPresent.modalPresentationStyle = style
toPresent.rootView = AnyView(
builder()
.environment(\.viewController, toPresent)
)
NotificationCenter.default.addObserver(forName: Notification.Name(rawValue: "dismissModal"), object: nil, queue: nil) { [weak toPresent] _ in
toPresent?.dismiss(animated: true, completion: nil)
}
self.present(toPresent, animated: true, completion: nil)
}
}
Normal Circumstances
Screen Mirroring
So I have an ios app using wkwebview - ionic framework, my images (not local and hosted) can be seen in ios 17 in my app but not in ios 18.
Is there some changes in wkwebview for this particular thing or am I missing something else?
The code for the issue is attached below.
Hello,
I am trying to implement a custom UICollectionViewLayout that does the following:
Everything works great for the most part, however I have encountered some unexpected animations when applying a new snapshot:
As you can see, any cell that contains a custom view with a height set with AutoLayout is scaled vertically before animating to it's intended height.
Here is a simple Xcode project that demonstrates the issue. Tap on the plus sign in the top right corner and watch the cells.
Example project: https://we.tl/t-9Y25NHzxiI
Custom UICollectionViewLayout code:
final class CustomLayout: UICollectionViewLayout {
struct PMCardContainerLayoutCell: Equatable {
var column: Int
var row: Int
}
// Configurable properties
public var numberOfColumns: Int = 6
public var cellHeight: Double = 100
public var cellSpacing: Double = 20
public var rowSpacing: Double = 20
public var sectionInsets: NSDirectionalEdgeInsets = .zero
public var layoutAttributes: [IndexPath: UICollectionViewLayoutAttributes] = [:]
override func prepare() {
super.prepare()
guard let collectionView else {
return
}
var updatedLayoutAttributes: [IndexPath: UICollectionViewLayoutAttributes] = [:]
let columnWidth: Double = (collectionView.bounds.width - cellSpacing * Double(numberOfColumns - 1) - sectionInsets.leading - sectionInsets.trailing) / Double(numberOfColumns)
let numberOfSections: Int = collectionView.numberOfSections
for section in 0..<numberOfSections {
var occupiedCells: [PMCardContainerLayoutCell] = []
var currentColumn: Int = 0
var currentRow: Int = 0
let numberOfItems: Int = collectionView.numberOfItems(inSection: section)
for item in 0..<numberOfItems {
let itemIndexPath = IndexPath(item: item, section: section)
let itemAttributes = UICollectionViewLayoutAttributes(forCellWith: itemIndexPath)
let itemSpanColumn = 1
let itemHeight = layoutAttributes[itemIndexPath]?.bounds.height ?? 140
let itemSpanRow = Int(ceil(itemHeight / (cellHeight + rowSpacing)))
let itemWidth = columnWidth * Double(itemSpanColumn) + cellSpacing * (Double(itemSpanColumn) - 1)
while true {
var itemDoesFit: Bool = true
if currentColumn + itemSpanColumn > numberOfColumns {
currentColumn = 0
currentRow += 1
}
for cell in 0..<itemSpanColumn {
if occupiedCells.contains(.init(column: currentColumn + cell, row: currentRow)) {
itemDoesFit = false
}
}
if itemDoesFit {
break
}
currentColumn += itemSpanColumn
}
if itemSpanRow > 1 {
for row in 1..<itemSpanRow {
for column in 0..<itemSpanColumn {
occupiedCells.append(.init(column: currentColumn + column, row: currentRow + row))
}
}
}
let originX = sectionInsets.leading + columnWidth * Double(currentColumn) + cellSpacing * Double(currentColumn)
let originY = + cellHeight * Double(currentRow) + rowSpacing * Double(currentRow)
itemAttributes.frame = CGRect(
x: originX,
y: originY,
width: itemWidth,
height: itemHeight
)
itemAttributes.zIndex = itemIndexPath.section * 10 + itemIndexPath.item
updatedLayoutAttributes[itemIndexPath] = itemAttributes
currentColumn += itemSpanColumn
}
}
layoutAttributes = updatedLayoutAttributes
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var allAttributes: [UICollectionViewLayoutAttributes] = []
for (_, attributes) in layoutAttributes {
if (rect.intersects(attributes.frame)) {
allAttributes.append(attributes)
}
}
return allAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return layoutAttributes[indexPath]
}
override var collectionViewContentSize: CGSize {
guard let collectionView else {
return .zero
}
let contentHeight: CGFloat = layoutAttributes.map({ $0.value.frame.maxY }).max() ?? 0
return CGSize(width: collectionView.bounds.width, height: contentHeight)
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool {
return originalAttributes.frame.height != preferredAttributes.frame.height
}
override func invalidationContext(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutInvalidationContext {
layoutAttributes[preferredAttributes.indexPath]?.frame.size = preferredAttributes.frame.size
let context = super.invalidationContext(forPreferredLayoutAttributes: preferredAttributes, withOriginalAttributes: originalAttributes)
return context
}
public override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
super.invalidateLayout(with: context)
if context.invalidateEverything || context.invalidateDataSourceCounts {
layoutAttributes.removeAll()
}
}
}
Anyone have any idea what I am doing wrong?
Thank you!
I have a UICalendarView that is embedded in a UIStackView. When I hide/show items in the stack and cause the height of the stack to change, the UICalendarView animates unexpectedly:
Note that the height and width of the UICalendarView remain unchanged and the animation appears to leave the actual content unchanged.
Interestingly, if I select a month with 6 weeks it does not do this animation:
Nothing I have tried has allowed me to avoid this distracting and unnecessary animation. Any thoughts as to why this is happening or, even better, is there anything I can do to avoid it?
Appreciate the help!
Hi, I'm running into a crash I can't wrap my head around. I'm using a collectionView with a compositional layout. Upon reloading the collection view via reloadData or reloading a particular section via reloadSection, customers are running into a crash I'm unable to reproduce.
The only information is this:
Fatal Exception: NSInternalInconsistencyException
UICollectionView internal inconsistency: missing final attributes for cell <UICollectionViewListCell: 0x13ad42a40; frame = (0 791; 768 40); layer = <CALayer: 0x28268e0e0>>; initial attributes: <UICollectionViewLayoutAttributes: 0x13ac5aa90> index path: (<NSIndexPath: 0xb33e6ceee91dbb86> {length = 2, path = 4 - 0}); frame = (0 791; 768 40); ; layout query: <UICollectionViewLayoutAttributes: 0x137db5190> index path: (<NSIndexPath: 0xb33e6ceee91dbb86> {length = 2, path = 4 - 0}); frame = (0 800; 768 44); ; collection view: <UICollectionView: 0x13d120a00; frame = (0 0; 768 904); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x2829ad7d0>; layer = <CALayer: 0x2827f1cc0>; contentOffset: {0, 0}; contentSize: {768, 1715}; adjustedContentInset: {0, 0, 0, 0}; layout: <UICollectionViewCompositionalLayout: 0x13ad2bd50>; dataSource: <drchrono_EHR.AppointmentDetailViewController: 0x139fff600>>
Any help would be greatly appreciated.
Thank you.
I have UISearchBar, when I run from xCode placeholder and icon is working well but when i run from simulator or testflight placeholder and icon is dissappear. How can I solve it?
Here my code:
view:
private lazy var searchBar: UISearchBar = {
let view = UISearchBar()
view.delegate = self
view.barTintColor = UIColor.clear
view.backgroundColor = UIColor.clear
view.isTranslucent = true
view.setBackgroundImage(UIImage(), for: .any, barMetrics: .default)
view.placeholder = "Search"
return view
}()
setup method:
view.addSubview(searchBar)
view.addSubview(collectionView)
searchBar.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).inset(17)
make.leading.trailing.equalToSuperview().inset(6)
make.height.equalTo(40)
}
Hello,
When using a UIImagePickerController with the .camera configuration I'm currently facing an issue where the delegate function imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) is not firing. But rather UIKit internals dismisses the parent view to the UIImagePickerController. I'm showing the picker controller through a UIViewControllerRepresentable.
It does not always occur however, and the behavior is very flakey, sometimes it fires when pressing the b, sometimes it does not.
When setting a breakpoint at the dismiss function when pressing the "Use Photo" button, UIKit internals dismisses the view, not my own code.