I am building an app about photos and
I want to create a photo sharing feature like Apple's Photos App.
Please see Steps to Reproduce and attached project.
The current share method has the following issues
The file name of the shared photo changes to “FullSizeRender”.
The creation and update dates of shared photos will change to the date they were edited or shared.
I want to ensure that the following conditions are definitely met
Share the latest edited version.
The creation date should be when the original photo was first created.
How can I improve the code?
STEPS TO REPRODUCE
class PHAssetShareManager {
static func shareAssets(_ assets: [PHAsset], from viewController: UIViewController, sourceView: UIView) {
let manager = PHAssetResourceManager.default()
var filesToShare: [URL] = []
let group = DispatchGroup()
for asset in assets {
group.enter()
getAssetFile(asset, resourceManager: manager) { fileURL in
if let fileURL = fileURL {
filesToShare.append(fileURL)
}
group.leave()
}
}
group.notify(queue: .main) {
self.presentShareSheet(filesToShare, from: viewController, sourceView: sourceView)
}
}
private static func getAssetFile(_ asset: PHAsset, resourceManager: PHAssetResourceManager, completion: @escaping (URL?) -> Void) {
print("getAssetFile")
let resources: [PHAssetResource]
switch asset.mediaType {
case .image:
if asset.mediaSubtypes.contains(.photoLive) {
// let editedResources = PHAssetResource.assetResources(for: asset).filter { $0.type == .fullSizePairedVideo }
// let originalResources = PHAssetResource.assetResources(for: asset).filter { $0.type == .pairedVideo }
let editedResources = PHAssetResource.assetResources(for: asset).filter { $0.type == .fullSizePhoto }
let originalResources = PHAssetResource.assetResources(for: asset).filter { $0.type == .photo }
resources = editedResources.isEmpty ? originalResources : editedResources
} else {
let editedResources = PHAssetResource.assetResources(for: asset).filter { $0.type == .fullSizePhoto }
let originalResources = PHAssetResource.assetResources(for: asset).filter { $0.type == .photo }
resources = editedResources.isEmpty ? originalResources : editedResources
}
case .video:
let editedResources = PHAssetResource.assetResources(for: asset).filter { $0.type == .fullSizeVideo }
let originalResources = PHAssetResource.assetResources(for: asset).filter { $0.type == .video }
resources = editedResources.isEmpty ? originalResources : editedResources
default:
print("Unsupported media type")
completion(nil)
return
}
guard let resource = resources.first else {
print("No resource found")
completion(nil)
return
}
let fileName = resource.originalFilename
let tempDirectoryURL = FileManager.default.temporaryDirectory
let localURL = tempDirectoryURL.appendingPathComponent(fileName)
// Delete existing files and reset cache
if FileManager.default.fileExists(atPath: localURL.path) {
do {
try FileManager.default.removeItem(at: localURL)
} catch {
print("Error removing existing file: \(error)")
}
}
let options = PHAssetResourceRequestOptions()
options.isNetworkAccessAllowed = true
resourceManager.writeData(for: resource, toFile: localURL, options: options) { (error) in
if let error = error {
print("Error writing asset data: \(error)")
completion(nil)
} else {
completion(localURL)
}
}
}
private static func presentShareSheet(_ items: [Any], from viewController: UIViewController, sourceView: UIView) {
print("presentShareSheet")
let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
if UIDevice.current.userInterfaceIdiom == .pad {
activityViewController.popoverPresentationController?.sourceView = sourceView
activityViewController.popoverPresentationController?.sourceRect = sourceView.bounds
}
viewController.present(activityViewController, animated: true, completion: nil)
}
}```
Explore the various UI frameworks available for building app interfaces. Discuss the use cases for different frameworks, share best practices, and get help with specific framework-related questions.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I'm facing an accessibility issue, where when I call UIViewController.addChild(_:) and pass in another instance of a UIViewController, the VoiceOver focus is jumping to the "Back" button in the navigation bar. How might one go about avoid this behaviour and having the accessibility/voiceover focus remain where it was at the time of adding the child?
After updating to NavigationStack with nested navigation links (with navigation links in a navigation destination), I see a lot of warnings in Xcode 14 beta:
Update NavigationAuthority bound path tried to update multiple times per frame.
Update NavigationAuthority possible destinations tried to update multiple times per frame.
The app often freezes when navigated with a NavigationLink. Do others see these problems?
Steps to reproduce:
From my UINavigationController, call
setViewControllers([vc1], animated: true)
Then later call
setViewControllers([vc2], animated: true)
Results:
In iOS 17, this behaves fine. In iOS 18, it crashes the UINavigationController.
Both log: Attempt to present * on * which is already presenting *
Workaround:
use setViewControllers(...animated: false)
Problem Description:
In a SwiftUI application, I've wrapped UIKit's UIPageViewController using UIViewControllerRepresentable, naming the wrapped class PagedInfiniteScrollView. This component causes navigation bar elements (title and buttons) to disappear.
This issue only occurs in Low Power Mode on a physical device.
Steps to Reproduce:
Enable Low Power Mode on a physical device and open the app's home page.
From the home page, open a detail sheet containing PagedInfiniteScrollView. This detail page include a navigation title and a toolbar button in the top-right corner. PagedInfiniteScrollView supports horizontal swiping to switch pages.
Tap the toolbar button in the top-right corner of the detail page to open an edit sheet.
Without making any changes, close the edit sheet and return to the detail page. On the detail page, swipe left and right on the PagedInfiniteScrollView.
Expected Result:
When swiping the PagedInfiniteScrollView, the navigation title and top-right toolbar button of the detail page should remain visible.
Actual Result:
When swiping the PagedInfiniteScrollView, the navigation title and top-right toolbar button of the detail page disappear.
import SwiftUI
@main
struct CalendarApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import SwiftUI
struct ContentView: View {
@State private var showDetailSheet = false
@State private var currentPage: Int = 0
var body: some View {
NavigationStack {
Button {
showDetailSheet = true
} label: {
Text("show Calendar sheet")
}
.sheet(isPresented: $showDetailSheet) {
DetailSheet(currentPage: $currentPage)
}
}
}
}
struct DetailSheet: View {
@Binding var currentPage: Int
@State private var showEditSheet = false
var body: some View {
NavigationStack {
PagedInfiniteScrollView(content: { pageIndex in
Text("\(pageIndex)")
.frame(width: 200, height: 200)
.background(Color.blue)
},
currentPage: $currentPage)
.sheet(isPresented: $showEditSheet, content: {
Text("edit")
})
.navigationTitle("Detail")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItemGroup(placement: .topBarTrailing) {
Button {
showEditSheet = true
} label: {
Text("Edit")
}
}
}
}
}
}
import SwiftUI
import UIKit
struct PagedInfiniteScrollView<Content: View>: UIViewControllerRepresentable {
typealias UIViewControllerType = UIPageViewController
let content: (Int) -> Content
@Binding var currentPage: Int
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
pageViewController.dataSource = context.coordinator
pageViewController.delegate = context.coordinator
let initialViewController = UIHostingController(rootView: IdentifiableContent(index: currentPage, content: { content(currentPage) }))
pageViewController.setViewControllers([initialViewController], direction: .forward, animated: false, completion: nil)
return pageViewController
}
func updateUIViewController(_ uiViewController: UIPageViewController, context: Context) {
let currentViewController = uiViewController.viewControllers?.first as? UIHostingController<IdentifiableContent<Content>>
let currentIndex = currentViewController?.rootView.index ?? 0
if currentPage != currentIndex {
let direction: UIPageViewController.NavigationDirection = currentPage > currentIndex ? .forward : .reverse
let newViewController = UIHostingController(rootView: IdentifiableContent(index: currentPage, content: { content(currentPage) }))
uiViewController.setViewControllers([newViewController], direction: direction, animated: true, completion: nil)
}
}
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var parent: PagedInfiniteScrollView
init(_ parent: PagedInfiniteScrollView) {
self.parent = parent
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let currentView = viewController as? UIHostingController<IdentifiableContent<Content>>, let currentIndex = currentView.rootView.index as Int? else {
return nil
}
let previousIndex = currentIndex - 1
return UIHostingController(rootView: IdentifiableContent(index: previousIndex, content: { parent.content(previousIndex) }))
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let currentView = viewController as? UIHostingController<IdentifiableContent<Content>>, let currentIndex = currentView.rootView.index as Int? else {
return nil
}
let nextIndex = currentIndex + 1
return UIHostingController(rootView: IdentifiableContent(index: nextIndex, content: { parent.content(nextIndex) }))
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed,
let currentView = pageViewController.viewControllers?.first as? UIHostingController<IdentifiableContent<Content>>,
let currentIndex = currentView.rootView.index as Int? {
parent.currentPage = currentIndex
}
}
}
}
extension PagedInfiniteScrollView {
struct IdentifiableContent<Content: View>: View {
let index: Int
let content: Content
init(index: Int, @ViewBuilder content: () -> Content) {
self.index = index
self.content = content()
}
var body: some View {
content
}
}
}
A timeline in RCP will post a notification "Identifier: Completed" when it finishes playing
I am trying to receive this in the following way:
extension Notification.Name {
static let notifyOnAnimationCompleted = Notification.Name("Completed")
}
// in the view
private let AnimationCompleted = NotificationCenter.default.publisher(for: .notifyOnAnimationCompleted)
RealityView {...}
.onReceive(AnimationCompleted)
{ _ in
print("End")
}
This was once working back to July, but it never prints "End" for now
Hello,
My app worked perfectly using Xcode 15.4. After updating to Xcode 16, my NavigationStack started to present the same screen twice using the following code:
@EnvironmentObject private var pathSports: NavigationRouteManager
var body: some View {
NavigationStack(path: $pathSports.path) {
....
}
.environmentObject(pathSports)
}
NavigationRouteManager:
import Foundation
import SwiftUI
class NavigationRouteManager: ObservableObject {
@Published var path = NavigationPath()
func jumpToRootView() {
path = NavigationPath()
}
}
I have had to change it to the following code for just a single screen to be displayed.
@State private var pathSports: NavigationPath = NavigationPath()
var body: some View {
NavigationStack(path: $pathSports) {
....
}
}
Have others experienced this same problem, or is it just me?
Many thanks.
Topic:
UI Frameworks
SubTopic:
SwiftUI
Hi all,
I’m working on a custom action sheet in SwiftUI that slides in and out from the bottom using .transition(.move(edge: .bottom)). However, I’ve encountered an issue where the transition doesn’t respect my specified animation duration.
Here’s the basic structure of my action sheet:
ZStack {
Color.black
.opacity(0.5)
VStack {
SomeActionSheetContent()
}
.transition(.move(edge: .bottom))
.zIndex(1)
}
Each button inside the action sheet triggers a dismissal using this code:
Button {
withAnimation(onDismissAnimation) { isPresented = false }
}
onDismissAnimation can be any Animation, but for demonstration, I’m using .linear(duration: 5).
The problem is that when dismissing the action sheet, the animation is significantly faster than expected, around 5 times faster. For example, a 0.35-second animation finishes in just a fraction of that time.
To investigate further, I tested with .transition(.move(edge: .top)), and in that case, the transition respects the specified animation duration perfectly.
Does anyone know why the transition behaves differently when dismissing to the bottom versus the top?
I think I'm misunderstanding something here, maybe someone could point me in the right direction.
In System Settings, "Correct spelling automatically" is DISABLED, along with all the other options that ruin what you're typing.
In TextEdit, it continues to highlight spelling and grammar mistakes, so does Mail, Safari and others.
Yet SwiftUI made apps, don't.
I can right-click, enable "Check Spelling while typing", but after typing a few characters, words, it becomes disabled again.
I've busted through the view hierarchy to get the hosted NSTextView and overridden "isContinuousSpellCheckingEnabled" but something frequently reverts this to false.
Is my only option to have a large TextEditor with spell checking (but not auto correction) to create my own version of TextEditor by hosting the NSTextView myself. That's a lot of work for something which seems wrong, when I'm hoping that I'm simply missing something here.
Xcode 15.2 on macOS 13.5.
I have a feature where I need to write some events to the calendar. It's not working saying Calendar is read only.
So I tried the sample app from Apple - Repeating lessions and drop in lessions from link below
https://developer.apple.com/documentation/eventkit/accessing_calendar_using_eventkit_and_eventkitui
Drop in sessions which uses EKEventEditViewController works fine.
But when I run Repeating lessions which requires calendar permissions it keeps saying Calendar is read only. I have hit allow on the access permissions alert and also check settings which shows app has required permissions.
can someone help why this is the case? if its a Calendar issue where do you set the modifiable permissions for it? How is EKEventEditViewController able to save events if the Calendar is readonly.
The TextField Shows and When selected opens the keyboard very slowly and before entering text closes immediately with these messages.
<PUICQuickboardController: 0x6000029111f0> [0x6000029111f0-1] presentation watchdog expired!
<PUICQuickboardController: 0x6000029111f0> [0x6000029111f0-1] is no longer the current presentation, we must have timed out...
This App was working fine in Simulator and test devices until recently.
I need to add an accessory view with a text field to an NSAlert. This works as expected but as my app comes in different languages, the alert looks a bit different depending on the language used:
So I wonder how I can make the text field automatically use the full width of the alert window. I tried to call the alert's layout method and then resize the text field but this seems not work in all cases. Any help would be highly appreciated. Thanks!
I want to dismiss a window when a user presses a button. I have tried the @Environment dismissWindow, however that's macOS 14+. My app requires compatibility with macOS 13 at minimum.
I would like to program an app in Swift Playground that displays WhatsApp Web in a webview, so to speak. But I keep seeing an error like in the screenshot.
My Code:
import SwiftUI
import WebKit
import PlaygroundSupport
struct WebView: UIViewRepresentable {
let url: URL
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
let request = URLRequest(url: url)
webView.load(request)
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
WebView(url: URL(string: "https://web.whatsapp.com")!)
.edgesIgnoringSafeArea(.all)
}
}
PlaygroundPage.current.setLiveView(ContentView())
I have created a progress indicator to simulate some progressing download task in the dock icon. However, I can see the progress bar appearing in the dock icon but it is not getting updated when I invoked the updateProgress() method. Ideally it should have updated it, and I m not able to figure out the reason?
I have creating the same NSProgressIndicator on an NSWindow and it works to update the progress bar with the same code. Anything that I m missing to understand here? Below is the code I m using:
class AppDelegate: NSObject, NSApplicationDelegate {
var progressIndicator: NSProgressIndicator!
let dockTile = NSApp.dockTile
func applicationWillFinishLaunching(_ notification: Notification) {
// Step 1: Create a progress bar (NSProgressIndicator)
progressIndicator = NSProgressIndicator(frame: NSRect(x: 10, y: 10, width: 100, height: 20))
progressIndicator.isIndeterminate = false
progressIndicator.minValue = 0.0
progressIndicator.maxValue = 100.0
progressIndicator.doubleValue = 0.0
progressIndicator.style = .bar
dockTile.contentView = progressIndicator
dockTile.display()
//// Update the progress bar for demonstration
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.updateProgress(50)
}
}
func updateProgress(_ value: Double) {
progressIndicator.doubleValue = value
NSApp.dockTile.display()
}
}
In SwiftUI's ViewModel class that are @Observable, is it necessary to annotate private fields as @ObservationIgnored?
I'm not sure if adding @ObservationIgnored to these fields will get performance gains, since there are no SwiftUI structs referencing these fields because they're private. However, I'd like to know what's the recommended approach here?
While this might not seem obvious for the example below, however, sometimes I have private fields that are changing pretty frequently. For these frequently changed fields, I think the performance gains will be larger.
Example:
@Observable
class UserProfileViewModel {
var userName: String?
var userPhoneNumber: String?
private var isFetchingData = false
}
vs
@Observable
class UserProfileViewModel {
var userName: String?
var userPhoneNumber: String?
@ObservationIgnored private var isFetchingData = false
}
I am working on an application that has some complex navigation needs, and I'm considering abandoning SwiftUI, at least in a few core areas of the app. I found SwiftUI intuitive and useful for simple things like:
Using a state to toggle the appearance or disappearance of an element
Using state to alter the look of UI elements
Watching the values of built-in interfaces like TextField or Slider
Controlling out-of-the-box UI elements like NavigationSplitView
But now I've got a UI that depends on a data model that can change at any time, and from that data I'll display lists of objects, and objects in detail, and all of this will be displayed according to a NavigationPath that depends on the data model's internal structure. This means that, for instance, I may be in the detail view of element X after having traveled to it from the detail view of element Y which was tapped in a list of XYZ, which was the result of tapping into part of the detail view of element W. This whole path could be invalidated by an update to the data model when it's received from the network. Furthermore, any element could have it's details changed, or be deleted, and I'd want the UI to reflect that right away if possible.
For the navigation work, I'm still optimistic that custom edits to a NavigationPath could be the way to go. However, for propagating data updates around my app, I know @State, @Bindable, etc are intended to handle this kind of thing, but when I'm try to send these bindings through the circuitous path of views I mentioned above, and I run into any unexpected hiccup, it feels like I'm at a disadvantage when debugging because I'm relying on opaque SwiftUI Magic to handle things under the hood in a way I don't really understand. I could put more effort into learning how this works, and potentially come up against some platform limitations, bugs, or other dead-ends, or I could just use Pub/Sub and a network of custom queues/pipes, and send updates around my app the old fashioned way, but using primitives whose functions are clear to me. I've been using SwiftUI for about a year and it still trips me up, and I have watched a few WWDC talks on the topic.
I'd appreciate any advice on this front from the frequenters of this forum. Thanks for reading my post.
I have a project with a single asset image. In the dependency package, I am attempting to preview using the image in the project using Bundle.main. I just get a blank.
How can I get this to work?
Sample project here: https://github.com/AaronBratcher/SwiftUIPreviewProblem
I encountered a crash with my SwiftUI app for macOS.
NSInvalidArgumentException: -[MTLIGAccelRenderCommandEncoder setVertexBuffer:offset:attributeStride:atIndex:]: unrecognized selector sent to instance
struct ContentView: View {
@State var text = ""
var body: some View {
TextEditor(text: $text)
}
}
When I run macOS app, console print
NSBundle file:///System/Library/PrivateFrameworks/MetalTools.framework/ principal class is nil because all fallbacks have failed
and I found it crash with TextEditor.
Can somebody help me?
Hello,
I came across a strange behaviour of GeometryReader in a NavigationStack. I want to get the size of an image view on the screen, so I add a .background modifier with a GeometryReader inside
.background( GeometryReader { geo in
Color.clear
.onAppear {
imgW = geo.size.width
imgH = geo.size.height
}
When I use this on a pure image view it works fine.
When this image is inside a NavigationStack I get wrong results when the image is shown the first time. But I get the right results when I move to the next view and come back to this view. Now GeometryReader gives the right size.
Is it a bug or do I miss something?
For demonstration I wrote a short program with 3 views:
import SwiftUI
class Router: ObservableObject {
@Published var path = NavigationPath()
}
enum Routes: String, CaseIterable, Hashable {
case View2, View3
}
struct ContentView: View {
@StateObject var router = Router()
@State private var imgSize = CGSize()
var body: some View {
NavigationStack(path: $router.path) {
VStack {
Image(uiImage: UIImage(named: "testpicture")!)
.resizable()
.scaledToFit()
.background( GeometryReader { geo in
Color.clear
.onAppear {
imgSize = geo.size
}
})
Text("Width \(Int(imgSize.width)) - Height \(Int(imgSize.height))")
Button {
router.path.append(Routes.View2)
} label: {
Text("Press for next view")
}
}
.navigationDestination(for: Routes.self) { route in
switch route {
case .View2:
View2()
case .View3:
View3()
}
}
}
.environmentObject(router)
}
}
struct View2: View {
@EnvironmentObject var routes: Router
@State private var imgSize = CGSize()
var body: some View {
VStack {
Image(uiImage: UIImage(named: "testpicture")!)
.resizable()
.scaledToFit()
.background( GeometryReader { geo in
Color.clear
.onAppear {
imgSize = geo.size
}
})
Text("Width \(Int(imgSize.width)) - Height \(Int(imgSize.height))")
Button {
routes.path.append(Routes.View3)
} label: {
Text("Press for next view")
}
}
.navigationTitle("View 2")
.navigationBarTitleDisplayMode(.inline)
}
}
struct View3: View {
@EnvironmentObject var routes: Router
var body: some View {
VStack {
Text("Hello World")
Text("& Friends")
}
.navigationTitle("View 3")
}
}
"testpicture" is a normal photo in the assets section.
Test in landscape mode (iPhone 15 plus simulator):
Startview shows the picture and "Width 0 - Height 0" (WRONG)
Press the button to show the next view
View2 shows the picture and "Width 285 - Height 380" (WRONG)
Going back to the startview shows "Width 269 - Height 359" (CORRECT)
Switching to View2 still gives the wrong results (285, 380)
Going to View3 and back to View2 gives the CORRECT result ("Width 236 - Height 315")
Same behaviour on a real device.
Side note: Going to View3 and back again to View2 gives on every transfer a warning: "Failed to create 0x132 image slot (alpha=1 wide=1) (client=0xe03620e7) [0x5 (os/kern) failure]"