Using previewCGImageRepresentation in both Xcode 13 and Xcode 12? - swift

I’ve run into an issue where Xcode 13b2 (iOS 15 SDK) changed the Swift return type of AVCapturePhoto.previewCGImageRepresentation(). In Xcode 12.5.1 (iOS 14 SDK), this method returns Unmanged<CGImage>. In 13b2 - 13b4, it returns CGImage?.
I need my code to compile under both Xcode versions, since Xcode 13 has other issues, and can’t be used to submit builds to the App Store. I thought I was clever writing this, but it won’t compile, because it’s not conditional code compilation check but rather a runtime check:
extension AVCapturePhoto {
func stupidOSChangePreviewCGImageRepresentation() -> CGImage? {
if #available(iOS 15, *) {
return self.previewCGImageRepresentation()
} else {
return self.previewCGImageRepresentation()?.takeUnretainedValue()
}
}
}
Another possibility might be to create a user-defined Xcode setting, but I don’t think that can be done conditionally based on Xcode or SDK version.
There might be some unsafe pointer histrionics one can do…
Any other ideas?

You can make a check on the swift compiler version. An exhaustive list is available on wikipedia at the time of writing.
https://en.wikipedia.org/wiki/Xcode#Toolchain_versions
extension AVCapturePhoto {
func stupidOSChangePreviewCGImageRepresentation() -> CGImage? {
#if compiler(>=5.5)
return self.previewCGImageRepresentation()
#else
return self.previewCGImageRepresentation()?.takeUnretainedValue()
#endif
}
}

Related

Swift multiple #available checks for bug in 2 versions of SDK

Apple had a bug in the SDK, which I needed to implement a workaround, they have since fixed that bug but not for two versions of the SDK. I would like the workaround to apply to just those two versions, but there does not seem to be a way of specifying that with #available. #available's intent is to check a version or later.
For example, there was a bug in 14.3 and 14.4, but that was fixed in 14.5, I would like the workaround to not be in effect after that. I came up with this, however, I am pretty sure that the second check is not going to work on 14.3. Is there a way to turn off the check after a version, or provide a range of versions? Also, you are not able to provide || or && the #available if or guard statements.
guard #available(iOS 14.3, *), #available(iOS 14.4, *) else { return }
// work around code.
The best I can come up with is this, but that workaround will be there from 14.3 onward:
guard #available(iOS 14.3, *) else { return }
// work around code.
The best answer I can think of is this:
if #available(iOS 14.5, *) {
} else if #available(iOS 14.3, *) {
// workaround code
}
I know it's ugly, but it works. Unfortunately, Swift does not seem to provide a way to give a maximum value for #available.
Edit: A cleaner method, but one that does not use #available:
let versionNumber = (UIDevice.current.systemVersion as NSString).floatValue
guard versionNumber >= 14.3 && versionNumber < 14.5 else { return }
// workaround code
Edit 2: In Swift 5.6 (Xcode 13.3), you can do:
guard #available(iOS 14.3, *), #unavailable(iOS 14.5) else { return }
// workaround code

Conditional Conformance to Hashable

With Xcode 10, and Swift 4.2 there are additional types that conform to Hashable as long as their elements also conform to Hashable (Array, Dictionary, etc).
I currently have some code in my project to add Hashable conformance for Swift 4.1 and below like this:
extension Array: Hashable where Element: Hashable {
public var hashValue: Int {
let prime = 31
var result = 1
for element in self {
result = prime * result + element.hashValue
}
return result
}
}
However, even if I add #if !swift(>=4.2) around the code I still see the same warning in Xcode.
My question is, how can I keep the conditional conformance to Hashable for Swift 4.1 and below, but silence the warning for Swift 4.2?
The conditional compilation statement #if swift(...) checks against the language version you're running with – which can differ from the compiler (and therefore standard library) version.
In your case, it sounds like you're using a Swift 4.2 compiler in Swift 4 compatibility mode, which gives you a language version of 4.1.50. This therefore passes the conditional compilation statement and your extension is compiled, giving you a duplicate conformance.
In order to check for a compiler version less than 4.2, you want the following:
// less than 4.2 in Swift 4 compat mode and (less than 4.2 in 3 compat mode or
// greater than 4).
#if !swift(>=4.1.50) && (!swift(>=3.4) || swift(>=4.0))
extension Array : Hashable where Element : Hashable {
public var hashValue: Int {
let prime = 31
var result = 1
for element in self {
result = prime * result + element.hashValue
}
return result
}
}
#endif
Things will be much better with the new #if compiler directive, which is available in Swift 4.2 from Xcode 10 beta 4 onwards (confirmed by #MartinR). With that, you can directly test for the compiler version, ignoring any compatibility modes it might be running in.
This doesn't help your specific case as you need the code to be understood by both Swift 4.2 and 4.1 compilers (as also pointed out by Martin), but for future compatibility problems, you could for example use #if !compiler(>=5) in order to only compile a block of code if using a 4.2 compiler.

Having trouble with MusicKit sample app provided by Apple

I am trying to build "Adding Content to Apple Music”, Music Kit sample app provided by Apple, on Xcode 9 beta 3. However I am having 4 errors like this : three “Ambiguous use of 'play()’” errors and one “Ambiguous use of 'pause()’”
Please tell me how to fix this if you already solved this problem.
func beginPlayback(itemCollection: MPMediaItemCollection) {
musicPlayerController.setQueue(with: itemCollection)
//Ambiguous use of 'play()’
musicPlayerController.play()
}
func beginPlayback(itemID: String) {
musicPlayerController.setQueue(with: [itemID])
//Ambiguous use of 'play()’
musicPlayerController.play()
}
// MARK: Playback Control Methods
func togglePlayPause() {
if musicPlayerController.playbackState == .playing {
//Ambiguous use of 'pause()’
musicPlayerController.pause()
} else {
//Ambiguous use of 'play()’
musicPlayerController.play()
}
}
I have found a similar question in the Apple's Dev Forums:
MPMusicPlayerController Swift4 - Ambiguous Use of Play
According to an entry writing a fix to work around the issue, you need to change this line in MusicPlayerManager.swift:
let musicPlayerController = MPMusicPlayerController.systemMusicPlayer
(musicPlayerController's type becomes MPMusicPlayerController & MPSystemMusicPlayerController with this code.)
To:
let musicPlayerController: MPMusicPlayerController = MPMusicPlayerController.systemMusicPlayer
(musicPlayerController is explicitly annotated as MPMusicPlayerController.)
In my opinion this is a bug of Swift related to SE-0156 Class and Subtype existentials and you should better send a bug report to Apple or swift.org.

Does _ArrayType or _ArrayProtocol not available in Swift 3.1?

I was using _ArrayType in my project when I was running on swift 2.1. I upgraded to swift 3.0.2 (Xcode 8.2.1) last week and I found here that _ArrayType is changed to _ArrayProtocol and it was working well.
Today I upgraded my Xcode to 8.3.1, and it gives me error:
Use of undeclared type '_ArrayProtocol'. Here is my code:
extension _ArrayProtocol where Iterator.Element == UInt8 {
static func stringValue(_ array: [UInt8]) -> String {
return String(cString: array)
}
}
What's wrong now? Why _ArrayProtocol is undeclared in swift 3.1 while it was working in swift 3.0.2.
Also when I look here in git I see _ArrayProtocol available.
Than I looked into Swift 2.1 docs I am able to see '_ArrayType' in protocol listing but in Swift 3.0/3.1 docs I am not able to see _ArrayProtocol.
Type names starting with an underscore should always treated as internal.
In Swift 3.1, it is marked as internal in the source code and therefore
not publicly visible.
Using _ArrayProtocol was a workaround in earlier Swift versions where
you could not define an Array extension with a "same type" requirement.
This is now possible as of Swift 3.1, as described in the
Xcode 8.3 release notes:
Constrained extensions allow same-type constraints between generic parameters and concrete types. (SR-1009)
Using the internal protocol is therefore not necessary anymore,
and you can simply define
extension Array where Element == UInt8 {
}
But note that your static func stringValue() does not need any
restriction of the element type. What you perhaps intended is to
define an instance method like this:
extension Array where Element == UInt8 {
func stringValue() -> String {
return String(cString: self)
}
}
print([65, 66, 67, 0].stringValue()) // ABC
Also note that String(cString:) expects a null-terminated sequence
of UTF-8 bytes.

withUnsafeBytes with Swift 3.1 and iOS 9.3

I use a method within my code to convert raw bytes to Ints.
The code I used previously was:
func convert(from data: Data) -> Int? {
return data.withUnsafeBytes { $0.pointee }
}
This used to work correctly, until XCode 8.3 with Swift 3.1 support came along. Now the same method works correctly on iOS 10 devices, but fails (returns absurdly large numbers) on devices (and simulators) running iOS 9.3.
Example code: Data(bytes: [0]).withUnsafeBytes { $0.pointee } as Int returns -4611686018427387903
Anyone experiencing the same issue?