NSAttributedStringKey.attachment versus NSAttachmentAttributeName - ios

I'm having problems with NSAttributedStringKey.attachment versus NSAttachmentAttributeName. Here's the relevant code:
var key: Any?
if #available(iOS 11, *) {
key = NSAttributedStringKey.attachment
}
else {
key = NSAttachmentAttributeName
}
One of two things are happening. In the actual place where I'm trying to use this code (a Cococapod of my own design, with a deployment target of iOS 8 and now building with Xcode 9), I get an error:
Type 'NSAttributedStringKey' (aka 'NSString') has no member 'attachment'
Or, if I just make a new example project and set the deployment target at iOS 8, I get:
'NSAttachmentAttributeName' has been renamed to 'NSAttributedStringKey.attachment'
This is not the behavior I'd expect with #available. Thoughts?

This String vs struct difference is between Swift 3 (uses Strings such as NSAttachmentAttributeName) and Swift 4 (uses struct static attributes such as NSAttributedStringKey.attachment), not between iOS <11 and iOS >=11. For instance, you can use NSAttributedStringKey.attachment and similar in any supporting version of iOS (e.g. .attachment is available since iOS 7) within a Swift 4 project. #available doesn't apply because it's a Swift language version difference rather than an OS version difference.
Ensure your pod is set to the correct Swift version and it should then work as expected. You can tell CocoaPods that by adding a .swift-version file at the top of your project:
$ echo 4.0 >.swift-version
This magical version file is mentioned in passing in a CocoaPods blog post from last year: http://blog.cocoapods.org/CocoaPods-1.1.0/

Related

iOS compatibility with Swift's String .isEmpty

What compatibility issues, if any, are there with using isEmpty on a string in Swift?
let str = "Hello, planet"
if !str.isEmpty {
print(str)
}
I'm developing with the latest Xcode+Swift versions, but I thought I once read (perhaps incorrectly) that .isEmpty will not work with older versions of iOS, and so str == "" should be used instead.
Thanks!
References (whose overlap confuses me some):
(String) "whether a string has no characters" ... From Protocol: Collection
(String) "whether the collection is empty"
(Range) "whether the collection is empty" ... SDK: Xcode 10+
The availability of isEmpty to check if a String is empty came into Swift, when String became a collection type with the release of Swift 4. So the isEmpty check on a String is quite similar to the isEmpty check on an Array, Dictionary or a Set.
And, features like these are not limited to the iOS or Xcode versions. These kind of changes are specific to the language version (Swift); so you can safely assume that it would work on all devices or OS which can run your application.
In the recent / latest version of Swift, you can just use the isEmpty, and it's the same with comparing == "".
but I thought I once read (perhaps incorrectly) that .isEmpty will not
work with older versions of iOS
The iOS/MacOS version is NOT related to the Swift version you are using (and you should use always the latest), and instead, some of the new provided APIs in Swift do support a minimum version of the iOS/MacOS. So don't be confused.

Cross platform code sharing in Swift

I have an app created from Xcode 9 / New Project / Cross Platform Game App, targets: macOS and iOS.
I wrote the following simple function to load text from a Data Asset.
func dataAssetAsString(_ name: String) -> String? {
if let asset = NSDataAsset(name: NSDataAsset.Name(name)) {
return String(data: asset.data, encoding: .utf8)
}
return nil
}
I'm puzzled by the following things:
This function only works if I import either UIKit, or AppKit.
But somehow importing MetalKit instead of UIKIt or AppKit makes it work. This really puzzles me. This should have nothing to do with MetalKit.
Most answers I found for code sharing between iOS and macOS suggest some kind of conditional import at the beginning of the file / shimming.
I went through the slides of WWDC 2014 Session 233: Sharing code between iOS and OS X, and it explicitly says we should not do shimming in Swift.
On the other hand, I don't understand what we should do instead of shimming. The mentioned very complicated process of creating and compiling shared frameworks for a 5 line function cannot be the right direction.
In 2018 (Swift 4, Xcode 9, iOS 11, macOS 10.13), what would be the recommended way to add such a trivial cross platform code to an app?
What is the magic of MetalKit which makes it work without shimming? Does it shim internally?

`UTF16Index()` or `UTF16Index.init(encodedOffset:)` for Xcode 8/9 support

I'm on a development team that's supporting both Xcode 8 and Xcode 9.
I was developing a feature that used String.UTF16Index(range.location) in Xcode 8. When I upgraded to Xcode 9, that resulted in the error 'init' is deprecated.
So in Xcode 9 I changed it to UTF16Index.init(encodedOffset: range.lowerBound). However, now that doesn't work in Xcode 8 with the error Argument labels '(encodedOffset:)' do not match any available overloads.
Even if I could check the Xcode version and write different code, one of the lines would fail at compile time. How can I manage this? Or am I stuck waiting until we fully move to Xcode 9?
From Version Compatibility in the Swift documentation:
NOTE
When the Swift 4 compiler is working with Swift 3 code, it identifies its language version as 3.2. As a result, you can use conditional compilation blocks like #if swift(>=3.2) to write code that’s compatible with multiple versions of the Swift compiler.
In your case that would be
#if swift(>=3.2)
let idx = String.UTF16Index(encodedOffset: range.lowerBound)
#else
let idx = String.UTF16Index(range.location)
#endif
In Xcode 9 (Swift 4 or Swift 3.2 mode) only the first line is compiled,
and in Xcode 8 (Swift 3.1) only the second line is compiled.
Update: The use of encodedOffset is considered harmful and will be deprecated in Swift 5. Starting with Swift 4, the correct way to convert an NSRange to a Range<String.Index> is
let str = "abc"
let nsRange = NSRange(location: 2, length: 1)
let sRange = Range(nsRange, in: str)
As others have said, you can do a Swift version check to call the right API. But if you're calling this from multiple places then it might be easier to define a shim
#if swift(>=3.2)
// already correct
#else
extension String.UTF16Index {
init(encodedOffset: Int) {
self.init(encodedOffset)
}
}
#endif
Now you can just write String.UTF16Index(encodedOffset: range.lowerBound) and in Xcode 8 it will call your shim.

ARSessionConfiguration unresolved in Xcode 9 GM

I have created an ARKit project using a beta version of Xcode 9, which I was able to run on my real device without issues.
Yesterday, I upgraded to Xcode 9 GM, and without touching anything, Xcode shows multiple errors, saying it does not know ARSessionConfiguration i.e.:
Use of undeclared type 'ARSessionConfiguration'
and:
Use of undeclared type 'ARWorldTrackingSessionConfiguration'
...for this code:
let session = ARSession()
var sessionConfig: ARSessionConfiguration = ARWorldTrackingSessionConfiguration()
I have imported ARKit and am using the ARSCNViewDelegate in my ViewController.
When opening the project from the beta version of Xcode, it does not show the errors and I can again run the app on my phone.
Any idea how I can fix this?
ARWorldTrackingSessionConfiguration has been deprecated and renamed to ARWorldTrackingConfiguration: See here
Also, ARSessionConfiguration has been deprecated and renamed to ARConfiguration, which is now an abstract base class.
Use AROrientationTrackingConfiguration when you don't want world tracking, instead of using a generic ARConfiguration. Thus:
let configuration = AROrientationTrackingConfiguration()
You can also check if world tracking is supported on a device:
if ARWorldTrackingConfiguration.isSupported {
configuration = ARWorldTrackingConfiguration()
}
else {
configuration = AROrientationTrackingConfiguration()
}
In Xcode 9 GM, looks like ARWorldTrackingSessionConfiguration has been renamed to ARWorldTrackingConfiguration:
https://developer.apple.com/documentation/arkit/arworldtrackingconfiguration
Reference to this change:
https://github.com/markdaws/arkit-by-example/issues/7
ARSessionConfiguration has been renamed to ARConfiguration:
https://developer.apple.com/documentation/arkit/arconfiguration

Swift App Extension: instance method count is unavailable

I just create my first app extension using XCode 7.1. One code file containing the code below is shared with both targets:
var str = "";
var l = str.count; //Compile error for extension target App: count is unavailable: There is no ...
The reason for this compile error seams to be that App extension compiles with swift 1.2 while the container target compiles with swift 2.0.
One solution would be importing the content App into the extension App doesn't appear to be a good solution from what i read about it. Sharing the code between targets can be difficult if both are not compiled using the same compiler.
I just run through all target settings and didn't find nothing that could be changed.
Can't find any post about this problem, witch is not so uncommon, so it is must likely i am interpreting something in a wrong way.
The only solution i can think of is using NSString instead of String but that is just an workaround for one class type. More problems of this kind will emerge in the future.
In Swift 2 it's
str.characters.count
Use str.characters.count to get String length in Swift 2

Resources