Hello everyone,
I've built a @CurrentValue property wrapper that mimics the behavior of @Published, allowing a property to publish values on "did set". I've also created my own assign(to:) implementation that works with @CurrentValue properties, allowing values to be assigned from a publisher to a @CurrentValue property.
However, I'm running into an issue. When I use this property wrapper with two classes and the source class (providing the publisher) is not stored as a property, the subscription is deallocated, and values are no longer forwarded.
Here's the property wrapper code:
@propertyWrapper
public struct CurrentValue<Value> {
/// A publisher for properties marked with the `@CurrentValue` attribute.
public struct Publisher: Combine.Publisher {
public typealias Output = Value
public typealias Failure = Never
/// A subscription that forwards the values from the CurrentValueSubject to the downstream subscriber
private class CurrentValueSubscription<S>: Subscription where S: Subscriber, S.Input == Output, S.Failure == Failure {
private var subscriber: S?
private var currentValueSubject: CurrentValueSubject<S.Input, S.Failure>?
private var cancellable: AnyCancellable?
init(subscriber: S, publisher: CurrentValue<Value>.Publisher) {
self.subscriber = subscriber
self.currentValueSubject = publisher.subject
}
func request(_ demand: Subscribers.Demand) {
var demand = demand
cancellable = currentValueSubject?.sink { [weak self] value in
// We'll continue to emit new values as long as there's demand
if let subscriber = self?.subscriber, demand > 0 {
demand -= 1
demand += subscriber.receive(value)
} else {
// If we have no demand, we'll cancel our subscription:
self?.subscriber?.receive(completion: .finished)
self?.cancel()
}
}
}
func cancel() {
cancellable = nil
subscriber = nil
currentValueSubject = nil
}
}
/// A subscription store that holds a reference to all the assign subscribers so we can cancel them when self is deallocated
fileprivate final class AssignSubscriptionStore {
fileprivate var cancellables: Set<AnyCancellable> = []
}
fileprivate let subject: CurrentValueSubject<Value, Never>
fileprivate let assignSubscriptionStore: AssignSubscriptionStore = .init()
fileprivate var value: Value {
get {
subject.value
}
nonmutating set {
subject.value = newValue
}
}
init(_ initialValue: Output) {
self.subject = .init(initialValue)
}
public func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
let subscription = CurrentValueSubscription(subscriber: subscriber, publisher: self)
subscriber.receive(subscription: subscription)
}
}
public var wrappedValue: Value {
get { publisher.value }
nonmutating set { publisher.value = newValue }
}
public var projectedValue: Publisher {
get {
publisher
}
mutating set {
publisher = newValue
}
}
private var publisher: Publisher
public init(wrappedValue: Value) {
publisher = .init(wrappedValue)
}
}
/// A subscriber that receives values from an upstream publisher and assigns them to a downstream CurrentValue property.
private final class AssignSubscriber<Input>: Subscriber, Cancellable {
typealias Failure = Never
private var receivingSubject: CurrentValueSubject<Input, Never>?
private weak var assignSubscriberStore: CurrentValue<Input>.Publisher.AssignSubscriptionStore?
init(currentValue: CurrentValue<Input>.Publisher) {
self.receivingSubject = currentValue.subject
self.assignSubscriberStore = currentValue.assignSubscriptionStore
}
func receive(subscription: Subscription) {
// Hold a reference to the subscription in the downstream publisher
// so when it deallocates, the susbcription is automatically cancelled
assignSubscriberStore?.cancellables.insert(AnyCancellable(subscription))
subscription.request(.unlimited)
}
func receive(_ input: Input) -> Subscribers.Demand {
receivingSubject?.value = input
return .none
}
func receive(completion: Subscribers.Completion<Never>) {
// Nothing to do here
}
public func cancel() {
receivingSubject = nil
assignSubscriberStore = nil
}
}
public extension Publisher where Self.Failure == Never {
/// Assigns the output of the upstream publisher to a downstream CurrentValue property
/// - Parameter currentValue: The CurrentValue property to assign the values to
func assign(to currentValue: inout CurrentValue<Self.Output>.Publisher) {
let subscriber = AssignSubscriber(currentValue: currentValue)
self.subscribe(subscriber)
}
}
Here’s an example demonstrating the issue, where two classes are used: Source, which owns the @CurrentValue property, and Forwarder1, which subscribes to updates from Source:
final class Source {
@CurrentValue public private(set) var value: Int = 1
func update(value: Int) {
self.value = value
}
}
final class Forwarder1 {
@CurrentValue public private(set) var value: Int
init(source: Source) {
self.value = source.value
source.$value.dropFirst().assign(to: &$value)
// The source is not stored as a property, so the subscription deallocates
}
func update(value: Int) {
self.value = value
}
}
With this setup, if source isn’t retained as a property in Forwarder1, the subscription is deallocated prematurely, and value in Forwarder1 stops receiving updates from Source.
However, this doesn’t happen with @Published properties in Combine. Even if source isn’t retained, @Published subscriptions seem to stay active, propagating values as expected.
My Questions:
What does Combine do internally with @Published properties that prevents the subscription from being deallocated prematurely, even if the publisher source isn’t retained as a property?
Is there a recommended approach to address this in my custom property wrapper to achieve similar behavior, ensuring the subscription isn’t lost?
Any insights into Combine’s internals or suggestions on how to resolve this would be greatly appreciated. Thank you!
Swift
RSS for tagSwift is a powerful and intuitive programming language for Apple platforms and beyond.
Posts under Swift tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hello everyone!
I'm having a problem with background tasks running in the foreground.
When a user enters the app, a background task is triggered. I've written some code to check if the app is in the foreground and to prevent the task from running, but it doesn't always work. Sometimes the task runs in the background as expected, but other times it runs in the foreground, as I mentioned earlier.
Could it be that I'm doing something wrong? Any suggestions would be appreciated.
here is code:
class BackgroundTaskService {
@Environment(\.scenePhase) var scenePhase
static let shared = BackgroundTaskService()
private init() {}
// MARK: - create task
func createCheckTask() {
let identifier = TaskIdentifier.check
BGTaskScheduler.shared.getPendingTaskRequests { requests in
if requests.contains(where: { $0.identifier == identifier.rawValue }) {
return
}
self.createByInterval(identifier: identifier.rawValue, interval: identifier.interval)
}
}
private func createByInterval(identifier: String, interval: TimeInterval) {
let request = BGProcessingTaskRequest(identifier: identifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: interval)
scheduleTask(request: request)
}
// MARK: submit task
private func scheduleTask(request: BGProcessingTaskRequest) {
do {
try BGTaskScheduler.shared.submit(request)
} catch {
// some actions with error
}
}
// MARK: background actions
func checkTask(task: BGProcessingTask) {
let today = Calendar.current.startOfDay(for: Date())
let lastExecutionDate = UserDefaults.standard.object(forKey: "lastCheckExecutionDate") as? Date ?? Date.distantPast
let notRunnedToday = !Calendar.current.isDate(today, inSameDayAs: lastExecutionDate)
guard notRunnedToday else {
task.setTaskCompleted(success: true)
createCheckTask()
return
}
if scenePhase == .background {
TaskActionStore.shared.getAction(for: task.identifier)?()
}
task.setTaskCompleted(success: true)
UserDefaults.standard.set(today, forKey: "lastCheckExecutionDate")
createCheckTask()
}
}
And in AppDelegate:
BGTaskScheduler.shared.register(forTaskWithIdentifier: "check", using: nil) { task in
guard let task = task as? BGProcessingTask else { return }
BackgroundTaskService.shared.checkNodeTask(task: task)
}
BackgroundTaskService.shared.createCheckTask()
In below Swift code , is there any possiblities of failure of Unmanaged.passRetain and Unmanaged.takeRetain calls ?
// can below call fail (constructor returns nil due to OS or language error) and do i need to do explicit error handling here?
let str = TWSwiftString(pnew)
// Increasing RC by 1
// can below call fail (assuming str is valid) and do i need to do explicit error handling for the same ?
let ptr:UnsafeMutableRawPointer? = Unmanaged.passRetained(str).toOpaque()
// decrease RC by 1
// can below call fail (assuming ptr is valid) ? and do i need to do explicit error handling
Unmanaged<TWSwiftString>.fromOpaque(pStringptr).release()
Hello everyone,
I’m developing an alarm app in Swift 5, and I’m running into an issue with playing custom alarm sounds. Here’s the setup:
I’m using AVAudioPlayer to play a custom sound when the alarm goes off.
I’m triggering the alarm through a local notification, which works perfectly while the app is in the foreground.
However, when the app is in the background or the screen is locked, the custom alarm sound doesn’t play.
I’ve looked at other apps on the App Store, like Alarmy, which seem to play alarms even when the iPhone is locked or the app is in the background. I’m trying to achieve similar functionality but haven’t been successful.
If anyone has experience with creating alarm apps or has a workaround for playing sounds in the background/lock screen, I’d really appreciate your insights. Are there specific permissions or settings I need to enable, or a different approach to handling sound playback?
Thank you so much in advance for your help!
On iOS you can create a new Lock Screen that contains a bunch of emoji, and they'll get put on the screen in a repeating pattern, like this:
When you have two or more emoji they're placed in alternating patterns, like this:
How do I write something like that? I need to handle up to three emoji, and I need the canvas as large as the device's screen, and it needs to be saved as an image. Thanks!
(I've already written an emojiToImage() extension on String, but it just plonks one massive emoji in the middle of an image, so I'm doing something wrong there.)
Hello, I recently have crashes on my application, it results in many crashes with different reasons, here are the different main reasons:
AttributeGraph: AG::Graph::update_main_refs(AG::AttributeID)
SwiftUICore: closure #1 in ViewLayoutEngine.explicitAlignment(_:at:)
SwiftUICore: __swift_instantiateGenericMetadata
My main problem is that these crashes appeared without any major changes to my app, and they never happen when I emulate the app from xcode whether on a simulator or a real device. I have other crashes with other errors I can provide them if necessary.
So I have a lot of trouble identifying where the errors come from, I tried to activate zombie objects, and address sanitizer without it revealing anything. Thanks in advance for the answers.
Hello, I have a small lightweight macOS application that includes a medium widget but the widget does not update with new data as often as I'd like. I understand that in apple's WidgetKit documentation they mention that apple controls when the widget updates due to battery life concerns, but I'd like to know if theres any way at all to control when the widget updates or when I think it makes sense to do so if I am not able to control how often it refreshes new data.
https://github.com/Alexx1105/MacStat-v2.1
I have implemented ShowInAppSearchResultsIntent and AppShortcutsProvider. But on iOS 18.1+ getting and error in console :- Failed to generate TargetContentIdentifier for criteria.
In iOS 18.0 it's working fine.
The code I have implemented
@AssistantIntent(schema: .system.search)
struct SearchIntent: ShowInAppSearchResultsIntent {
// static let title: LocalizedStringResource = "Search in Cineverse for"
static let searchScopes: [StringSearchScope] = [.general]
@Parameter(requestValueDialog: IntentDialog("What would you like to search for?"))
var criteria: StringSearchCriteria
@MainActor
func perform() async throws -> some IntentResult {
let searchString = criteria.term
print("Searching for \(searchString)")
return .result()
}
}
class AppShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: SearchIntent(),
phrases: [
"using \(.applicationName) search for",
"search on \(.applicationName) app"
],
shortTitle: "Search Movie",
systemImageName: "magnifyingglass"
)
}
}
In Swift 6, stricter concurrency rules can lead to challenges when making SwiftUI views conform to Equatable. Specifically, the == operator required for Equatable must be nonisolated, which means it cannot access @MainActor-isolated properties. This creates an error when trying to compare views with such properties:
Error Example:
struct MyView: View, Equatable {
let title: String
let count: Int
static func ==(lhs: MyView, rhs: MyView) -> Bool {
// Accessing `title` here would trigger an error due to actor isolation.
return lhs.count == rhs.count
}
var body: some View {
Text(title)
}
}
Error Message:
Main actor-isolated operator function '==' cannot be used to satisfy nonisolated protocol requirement; this is an error in the Swift 6 language mode.
Any suggestions?
Thanks
FB: FB15753655 (SwiftUI View cannot conform custom Equatable protocol in Swift 6.)
Hi,
I'm using the Network framework to browse for devices on the local network.
Unfortunately, I get many crash reports that crash in nw_browser_cancel, of which two are attached.
This discussion seems to have a similar issue, but it was never resolved: https://forums.developer.apple.com/forums/thread/696037
Contrary to the situation in the linked thread, my implementation uses DispatchQueue.main as the queue for the browser, so I don't think over-releasing the queue is the problem.
I am unable to reproduce this problem myself, but one of my users can reproduce it reliably it seems.
How can I resolve this crash?
2024-11-10_14-24-35.3886_+0100-4fdbdb8e944a4b655d60df53da3aa8c759f4fd1f.crash
2024-11-08_08-54-31.6366_+0100-303cabefb74bf89cdea3127b1cad122ee46016f2.crash
Hi,
I'm trying to create a UICollectionView where the cell high is automatic. Cells contains a UILabel with all anchors to the contentView of the cell.
It seems to work but I have a strange behavior with longer text, on reload data and on device rotation: Cells do not display the whole text or they change row, both randomly.
To create my collection view I first create the collection view with a custom flow layout setting the automatic size on viewWillAppear:
let collectionViewFlowLayout = CustomFlowLayout()
collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout)
and I have also overridden:
override func willAnimateRotation(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
collectionView.setNeedsLayout()
self.collectionView.layoutIfNeeded()
self.collectionView.collectionViewLayout.invalidateLayout()
//self.collectionView.reloadData()
}
Then, I created the custom layout as follow:
import UIKit
final class CustomFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
layoutAttributesObjects?.forEach({ layoutAttributes in
if layoutAttributes.representedElementCategory == .cell {
if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
})
return layoutAttributesObjects
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let collectionView = collectionView else {
fatalError()
}
guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
return nil
}
layoutAttributes.frame.origin.x = sectionInset.left
if(indexPath.section == 0){
layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
} else if (indexPath.section == collectionView.numberOfSections - 1){
let width = ScreenUtility.getCollectionCellWidthForElement(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing, multiplier: 3)
layoutAttributes.frame.origin.x = ScreenUtility.getCollectionCellOriginForElement(in: collectionView, at: indexPath, forElementHavingWidth: width, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing, multiplier: 3)
layoutAttributes.frame.size.width = width
} else if (indexPath.section == collectionView.numberOfSections - 3) || (indexPath.section == collectionView.numberOfSections - 4){
let width = ScreenUtility.getCollectionCellWidthForElement(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing)
layoutAttributes.frame.origin.x = ScreenUtility.getCollectionCellOriginForElement(in: collectionView, at: indexPath, forElementHavingWidth: width, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing)
layoutAttributes.frame.size.width = width
} else {
let width = ScreenUtility.getCollectionCellSizeForElementFullRow(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right)
layoutAttributes.frame.origin.x = ScreenUtility.getCollectionCellOriginForElementFullRow(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right)
layoutAttributes.frame.size.width = width
}
return layoutAttributes
}
}
And finally on collection view cells:
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return layoutAttributes
}
override func prepareForReuse() {
self.nameLabel.text = ""
self.idLabel.text = ""
self.contentView.setNeedsLayout()
self.contentView.layoutIfNeeded()
}
Let me show you an example on the iPad that is the worst.
First Time I open the collection view I have cells on wrong rows and not sized properly
Then I rotate the device portrait and the cells are fine
On landscape again it changes behavior:
This is just an example, things happens apparently randomly, and also sometimes cells disappear (I think the height is set to 0).
I really do not understand why, cells width seems to be computed correctly, and cell label is set via setter:
open var step: String = "" {
didSet {
nameLabel.text = step
nameLabel.sizeToFit()
self.contentView.setNeedsLayout()
self.contentView.layoutIfNeeded()
}
}
When using conformance to ObservableObject and then doing async work in a Task, you will get a warning courtesy of Combine if you then update an @Published or @State var from anywhere but the main thread. However, if you are using @Observable there is no such warning.
Also, Thread.current is unavailable in asynchronous contexts, so says the warning. And I have read that in a sense you simply aren't concerned with what thread an async task is on.
So for me, that begs a question. Is the lack of a warning, which when using Combine is rather important as ignoring it could lead to crashes, a pretty major bug that Apple seemingly should have addressed long ago? Or is it just not an issue to update state from another thread, because Xcode is doing that work for us behind the scenes too, just as it manages what thread the async task is running on when we don't specify?
I see a lot of posts about this from around the initial release of Async/Await talking about using await MainActor.run {} at the point the state variable is updated, usually also complaining about the lack of a warning. But ow years later there is still no warning and I have to wonder if this is actually a non issue. On some ways similar to the fact that many of the early posts I have seen related to @Observable have examples of an @Observable ViewModel instantiated in the view as an @State variable, but in fact this is not needed as that is addressed behind the scenes for all properties of an @Observable type.
At least, that is my understanding now, but I am learning Swift coming from a PowerShell background so I question my understanding a lot.
Im building an recipe app for the social media of my mother. i already have the functionality for the users, when a user gets created an empty array gets initiated at the database named favoriteRecipes, which stores the id of his favorite recipes to show in a view.
This is my AuthViewModel which is relevant for the user stuff:
import Firebase
import FirebaseAuth
import FirebaseFirestore
protocol AuthenticationFormProtocol {
var formIsValid: Bool { get }
}
@MainActor
class AuthViewModel : ObservableObject {
@Published var userSession: FirebaseAuth.User?
@Published var currentUser: User?
@Published var currentUserId: String?
init() {
self.userSession = Auth.auth().currentUser
Task {
await fetchUser()
}
}
func signIn(withEmail email: String, password: String) async throws {
do {
let result = try await Auth.auth().signIn(withEmail: email, password: password)
self.userSession = result.user
await fetchUser() // fetch user sonst profileview blank
} catch {
print("DEBUG: Failed to log in with error \(error.localizedDescription)")
}
}
func createUser(withEmail email: String, password: String, fullName: String) async throws {
do {
let result = try await Auth.auth().createUser(withEmail: email, password: password)
self.userSession = result.user
let user = User(id: result.user.uid, fullName: fullName, email: email)
let encodedUser = try Firestore.Encoder().encode(user)
try await Firestore.firestore().collection("users").document(result.user.uid).setData(encodedUser)
await fetchUser()
} catch {
print("Debug: Failed to create user with error \(error.localizedDescription)")
}
}
func signOut() {
do {
try Auth.auth().signOut() // sign out user on backend
self.userSession = nil // wipe out user session and take back to login screen
self.currentUser = nil // wipe out current user data model
} catch {
print("DEBUG: Failed to sign out with error \(error.localizedDescription)")
}
}
func deleteAcocount() {
let user = Auth.auth().currentUser
user?.delete { error in
if let error = error {
print("DEBUG: Error deleting user: \(error.localizedDescription)")
} else {
self.userSession = nil
self.currentUser = nil
}
}
}
func fetchUser() async {
guard let uid = Auth.auth().currentUser?.uid else { return }
currentUserId = uid
let userRef = Firestore.firestore().collection("users").document(uid)
do {
let snapshot = try await userRef.getDocument()
if snapshot.exists {
self.currentUser = try? snapshot.data(as: User.self)
print("DEBUG: current user is \(String(describing: self.currentUser))")
} else {
// Benutzer existiert nicht mehr in Firebase, daher setzen wir die userSession auf nil
self.userSession = nil
self.currentUser = nil
}
} catch {
print("DEBUG: Fehler beim Laden des Benutzers: \(error.localizedDescription)")
}
}
}
This is the code to fetch the favorite recipes, i use the id of the user to access the collection and get the favoriteRecipes out of the array:
import SwiftUI
@MainActor
class FavoriteRecipeViewModel: ObservableObject {
@Published var favoriteRecipes: [Recipe] = []
@EnvironmentObject var viewModel: AuthViewModel
private var db = Firestore.firestore()
init() {
Task {
await fetchFavoriteRecipes()
}
}
func fetchFavoriteRecipes() async{
let userRef = db.collection("users").document(viewModel.userSession?.uid ?? "")
do {
let snapshot = try await userRef.collection("favoriteRecipes").getDocuments()
let favoriteIDs = snapshot.documents.map { $0.documentID }
let favoriteRecipes = try await fetchRecipes(recipeIDs: favoriteIDs)
} catch {
print("DEBUG: Failed to load favorite recipes for user: \(error.localizedDescription)")
}
}
func fetchRecipes(recipeIDs: [String]) async throws -> [Recipe] {
var recipes: [Recipe] = []
for id in recipeIDs {
let snapshot = try await db.collection("recipes").document(id).getDocument()
if let recipe = try? snapshot.data(as: Recipe.self) {
recipes.append(recipe)
}
}
return recipes
}
}
Now the Problem occurs at the build of the project, i get the error
SwiftUICore/EnvironmentObject.swift:92: Fatal error: No ObservableObject of type AuthViewModel found. A View.environmentObject(_:) for AuthViewModel may be missing as an ancestor of this view.
I already passed the ViewModel instances as EnvironmentObject in the App Struct.
import SwiftUI
import FirebaseCore
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
@main
struct NimetAndSonApp: App {
@StateObject var viewModel = AuthViewModel()
@StateObject var recipeViewModel = RecipeViewModel()
@StateObject var favoriteRecipeViewModel = FavoriteRecipeViewModel()
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(viewModel)
.environmentObject(recipeViewModel)
.environmentObject(favoriteRecipeViewModel)
}
}
}
While customizing ImagePicker and using it, we find out that the metadata is not reflected normally and report it.
The situation is as follows.
The time or time zone of an image is changed in the Photos app.
Changing the time zone of an image with an actual capture date of 2024:11:08 08:27:44 → 2024:11:07 17:27:44
Image data is extracted from a PHAsset using PHImageManager.
The metadata is obtained from this image data.
The time zone information exposed in the Exif tag information does not reflect the time or time zone changed in the Photos app.
let asset: PHAsset = ...
....
let options = PHImageRequestOptions()
options.isSynchronous = true
options.version = .current
options.deliveryMode = .highQualityFormat
options.resizeMode = .none
options.normalizedCropRect = .zero
options.isNetworkAccessAllowed = true
options.progressHandler = { progress, error, _, _ in }
PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { imageData, uti, orientation, info in
let cgImageSource = CGImageSourceCreateWithData(imageData! as CFData, nil)
let properties = CGImageSourceCopyPropertiesAtIndex(cgImageSource!, 0, nil) as? Dictionary<String, Any>
let exif = properties!["{Exif}"]
let dictionary = exif as? Dictionary<String, Any>
}
Metadata Check
In this case, it is reflected in the creationDate of PHAsset, so it can be somewhat compensated by forcibly replacing the metadata.
However, because PHAsset does not include time zone information, when changing the time zone as well, it's impossible to calculate the correct time according to the time zone.
PHPicker
This issue is resolved when using the PHPickerResult provided by PHPicker.
extension PhotosPickerViewController: PHPickerViewControllerDelegate {
public func picker(_ picker: PHPickerViewController,
didFinishPicking results: [PHPickerResult]) {
.....
for result in results {
let identifier = UTType.image.identifier
if result.itemProvider.hasItemConformingToTypeIdentifier(identifier) {
result.itemProvider.loadDataRepresentation(forTypeIdentifier: identifier) { data, error in
guard let data = data,
let cgImageSource = CGImageSourceCreateWithData(data as CFData, nil),
let properties = CGImageSourceCopyPropertiesAtIndex(cgImageSource, 0, nil) as? Dictionary<String, Any>,
let exif = properties["{Exif}"],
let dictionary = exif as? Dictionary<String, Any>
else {
return
}
}
}
}
}
}
Metadata Check
Question
I wonder why this happens, and if this is normal behavior.
Instead of the System Picker that Apple provides as a base, I wonder if there is any way I can supplement it in that situation if I use a customizer.
I'm using this library for encoding / decoding RSA keys. https://github.com/Kitura/BlueRSA
It's worked fine up until macOS sequoia. The issue I'm having is the tests pass when in Debug mode, but the moment I switch to Release mode, the library no longer works.
I ruled this down the swift optimization level.
If I change the Release mode to no optimization, the library works again. Wondering where in the code this could be an issue? How would optimization break the functionality?
Hi all,
Background:
I am working as a library developer and would like to enable Swift C++ interoperability in our library. Our library supports both CocoaPods and SPM.
Question:
I would like to know whether it is possible to avoid breaking changes bring to the library users after enabling Swift C++ interoperability.
In my experiment, all apps and packages depend on the library needs to enable interoperability in Xcode or package manage tools, otherwise the source code cannot be complied.
I am wondering is there any ways to bypass this? For example, is there a way to only enable Swift C++ interoperability only in our libraries?
JavascriptCore doesn't come with any support for setTimeout (which is an API on the window/DOM object not provide din JavaScriptCore). So you need to implement a version of this yourself - no worries on that (there are examples to refer to out there, so it is fairly easy to set this up).
But I have come across an issue with this that I am not sure how to handle properly. It relates to when the timer callback fires and runs code in the JavaScript engine itself.
Consider this code snippet (assume I have provided an implementation of setTimeout):
console.log('Hello - here we go');
setTimeout(() => {
console.log('Hi from setTimeout callback ...');
}, 0);
Promise.resolve().then(() => {
console.log('Hi from promise');
});
console.log('Hi from main block');
In Node.js or say Safari, I would see this output:
Hello - here we go
Hi from main block
Hi from promise
Hi from setTimeout callback ...
So the promise then() is handled before the settimeout callback is handled. I think this is basically because Promise then() handlers are pushed onto something like a microtask queue, and the setttimeout callbacks on a separate queue, and the microtask queue is emptied before any other queue is processed (after completing the current event loop of course).
But when I implement this in JavaScript core, I don't always see the above - instead I can have:
Hello - here we go
Hi from main block
Hi from setTimeout callback ...
Hi from promise
So the timeout callback can be run BEFORE the promise handler. This obviously is different from Node or Safari.
Now I assume that is because the timeout callback is triggered from Swift native code that uses the call() API on a JSValue object that is provided when the settimeout is given to the native layer to process. And it seems that when native code attempts to execute JavaScript code (via call() or similar) then this is just executed as soon as possible - obviously not interrupting the Javascript core when executing any current event loop code, but potentially running between the point when the Javascript core finishes a normal event loop cycle and then starts processing the queued promise handlers.
This means that code that runs nicely in Node (for example) might not work the same way due to this behaviour.
Also, I also notice another thing: if JavaScript code makes a call to a native-provided method (e.g. by calling the setTimeout I show above, which I implement via a native-side handler) then during that call from JavaScript, it is possible for the native side to execute a call() and run Javascript code it wants. Again this is not what would happen in Node or Safari: it is not possible for timeouts (or network completions) to interrupt any 'builtin' function call, but in JavascriptCore it certainly is (to get around this I set a flag on the JavaScript side indicating a native call is being made, and if any native-triggered callback occurs on the javascript side when this flag is set, I have to 'queue' it via a promise handler for execution AFTER the current event loop is complete).
Are these known issues with Javascript core and are there ways to get around them?
thanks
macOS: Sequoia
Xcode: 16.1
I am working on a macOS app and it has a widget feature.
When I use Swift 6 (Build Settings > Swift Language Version) in IntentExtension, the intent configuration won't show up in macOS Sequoia.
If I downgrade to Swift 5, it works without any other changes.
Is it a bug or am I missing something? How can I use Swift 6 with IntentExtension.
We recently converted an existing app to adopt scenes (for CarPlay). The app has one main view controller that presents a WKWebView to show our content. Everything works fine in both landscape and portait mode when swiping on the screen to scroll. But with an iPad using an external Magic Keyboard, once you rotate to landscape mode the scrolling gestures are reversed. Swiping vertically on the trackpad is scrolling the page horizontally and vice versa. When this happens an error like below is logged (this error also shows up when in portait mode, but scrolling works as expected):
Unexpected window orientation: <UIWindow: 0x10370d8f0; orientation: landscapeLeft (4)> {
hidden = NO;
frame = {{0, 0}, {1180, 820}};
bounds = {{0, 0}, {1180, 820}};
ownsOrientation = NO;
ownsOrientationTransform = NO;
autorotationDisabled = NO;
windowInterfaceOrientation = unknown (0);
rootTransformOrientation = landscapeLeft (4);
viewTransformOrientation = unknown (0);
autorotationDisabled = NO;
orientationVC = ... {
providedSupportedOrientations = ( Pu Ll Lr Pd );
resolvedSupportedOrientations = ( Pu Ll Lr Pd );
canPreferOrientation = NO;
};
}, event type: 6
It seems to suggest that while the view controller orientation is set correctly. It doesn't know what the orientation is for the window. I have been unable to figure out what would change with adopting scenes that would explain this behavior. I assume it has to do with the potential multi-window nature of it but haven't found any docs that describe how to ensure the window is setup to use the same orientation as the device. Any suggestions on things to check?
I'll describe my crash with an example, looking for some insights into the reason why this is happening.
@objc public protocol LauncherContainer {
var launcher: Launcher { get }
}
@objc public protocol Launcher: UIViewControllerTransitioningDelegate {
func initiateLaunch(url: URL, launchingHotInstance: Bool)
}
@objc final class LauncherContainer: NSObject, LauncherContainer, TabsContentCellTapHandler {
...
init(
...
) {
...
super.init()
}
...
//
// ContentCellTapHandler
//
public func tabContentCellItemDidTap(
tabId: String
) {
...
launcher.initiateNewTabNavigation(
tabId: tabId // Crash happens here
)
}
public class Launcher: NSObject, Launcher, FooterPillTapHandler {
public func initiateNewTabNavigation(tabId: String) {
...
}
}
public protocol TabsContentCellTapHandler: NSObject {
func tabContentCellItemDidTap(
tabId: String,
}