hittest does not detect geometries hidden behind others in ios11 - swift - ios

I couldn't get hitTest (with no options) to detect geometries that are hidden behind some other geometry in iOS 11. My code worked fine on iOS 10. Anyone know how to fix?
Example:
let hitResults = scnView.hitTest(location, options: nil)
Should return several nodes - but does only return one node.

You should use the symbolic constant SCNHitTestSearchMode.all instead of 1, it's more descriptive.
if #available(iOS 11.0, *) {
hitResults = scnView.hitTest(location, options: [.searchMode: SCNHitTestSearchMode.all.rawValue]) }
}
The other options are .closest and .any.

Adding some additional details - in my experience, there has been a major change from iOS 10 to iOS 11 in the way SceneKit handles touches. Specifically, the DEFAULT operation in SceneKit, as Bernd notes above, is now that only the first node touched in the "ray" is returned in the [SCNHitTestResult].
The additional comment is that if you were hoping for backward compatibility to iOS 10 or before, I couldn't seem to get it to work, because the solution noted above requires iOS 11 Deployment Target. So Apple seems to have changed the default way touches are handled, and if you want it to work the original way, you must change the default of [SCNHitTestOption.searchMode : 1], which is only available if/when you change your Deployment Target to iOS 11 or higher. ( thanks, Apple)
Here are some futher oddities I found as I searched for a way to make an iOS 10 deployment work with Xcode 9 / iOS 11 updates. (note: I had upgraded my phone to iOS 11 when testing these scenarios with an iOS 10.3 Deployment Target build)
the [SCNHitTestOption.firstFoundOnly : 0], while available with iOS 10 deployment, seems to be ignored if .searchMode isn't also set to 1, which requires iOS 11
similarly [SCNHitTestOption.categoryBitMask : ], while avail with iOS 10 deployment, seems to be ignored if .searchMode isn't also set to 1...
Bottom line, from what I can tell, is that Apple does everything in its power to force devs to upgrade to the latest OS (either wittingly or unwittingly), which then "encourages" end users to have to upgrade to get the latest app updates.

I was able to find a fix - and will share it here, maybe its useful for somebody else:
Apple introduced this new searchMode - which is by default "closest" - you can get the old behavior by setting searchMode to ALL = 1
if #available(iOS 11.0, *) {
hitResults = scnView.hitTest(location, options: [SCNHitTestOption.searchMode: 1])
}

Or in Objective C...
options:#{SCNHitTestOptionSearchMode : [NSNumber numberWithInt:1]}

Related

Getting Error while upgrading to new Xcode 12

My App is using CoreLocation and CLLocationManager and is working fine in iOS 13 and iOS 12.
I have implemented new feature of Precise Location in iOS 14 using Xcode 12 and its working fine in iOS 14, iOS 13, iOS 12.
But When I execute ths Xcode 12 code in Xcode 11 version (Xcode 11.7) then I am getting error
Cannot infer contextual base in reference to member 'reducedAccuracy'
Value of type 'CLLocationManager' has no member 'accuracyAuthorization'
if #available(iOS 14.0, *) {
if authorizationStatus.accessLevel == .granted && locationManager.accuracyAuthorization == .reducedAccuracy {
return .locationAlwaysAllowPreciseLocationOff
}
if authorizationStatus.accessLevel == .denied && locationManager.accuracyAuthorization == .fullAccuracy {
return .locationDeniedPreciseLocationON
}
}
// MARK: iOS 14 location function.
#available(iOS 14.0, *)
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
// iOS 14 Location Delegate method, not available in iOS 13 version
}
and here the error is
Static member 'authorizationStatus' cannot be used on instance of type 'CLLocationManager'
As i Know Precise Location is feature of iOS 14 and its not available in below versions and "accuracyAuthorization", ".reducedAccuracy", ".fullAccuracy" is not available in iOS 13 versions.
My Question is how can i make my code run in Xcode 11 versions. I have already added the isAvailable check to check the device version.
Thanks in advance :)
No amount of #available or #available marking is going to help you in this situation.
Why not? Well, you're doing an unexpected thing: you are opening an Xcode 12 project in Xcode 11. Your code was compiled originally in Xcode 12, where iOS 14 is a thing. So it compiled successfully. But now you open the same project in Xcode 11, where iOS 14 is not a thing. Nothing about this environment has the slightest idea that it exists. Therefore, code that involves something unique to iOS 14 will not compile. If the compiler sees that code, you are toast.
So is all hope lost? Not quite! Suppose we were to hide the code from the compiler. If we do that — if we can arrange things so that, in Xcode 11, the compiler never sees this code at all — then we will be able to compile in Xcode 11.
Well, we can do that! We can use a compilation condition. All we need is some condition that we are allowed to check against, that will distinguish what version of Xcode this is. And there is such a condition — the Swift version.
So, we can write this, for example:
class ViewController: UIViewController {
let manager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
#if swift(>=5.3)
let status = manager.authorizationStatus
print(status.rawValue)
#endif
}
}
That code compiles in both Xcode 12 and Xcode 11, because in Xcode 11 the compilation condition fails, and the compiler never even looks inside the #if block.
In fact, we can provide an alternative version of the code, to be used in Xcode 11. In order to make this work as we desire, we will also have to restore your #available check, because we have to make the project's deployment target iOS 13, and the Xcode 12 compiler will complain if we don't protect the iOS 14 code:
class ViewController: UIViewController {
let manager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
#if swift(>=5.3)
if #available(iOS 14.0, *) {
let status = manager.authorizationStatus
print(status.rawValue)
}
#else
let status = CLLocationManager.authorizationStatus()
print(status.rawValue)
#endif
}
}
That code compiles and behaves correctly in either Xcode 11 or Xcode 12. Do you understand why? Let's review, because it's a bit tricky.
In Xcode 11, the whole #if section is never seen by the compiler. It sees only this:
let status = CLLocationManager.authorizationStatus()
print(status.rawValue)
That's good iOS 13 code, so all is well.
In Xcode 12, the whole #else section is never seen by the compiler. It sees only this:
if #available(iOS 14.0, *) {
let status = manager.authorizationStatus
print(status.rawValue)
}
That's good iOS 14 code, because, even though our project's deployment target is iOS 13, we have calmed the compiler's nerves by guaranteeing that this code won't execute in iOS 13 (where it would crash if it did execute).
Having said all that, the real answer is: don't. Everything I just did is way too much trouble! Once you've written code under Xcode 12, don't try to open that project in Xcode 11. That's not the way to test for backward compatibility.

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

NSAttributedStringKey.attachment versus NSAttachmentAttributeName

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/

UIView/UICoordinateSpace convert rect/point clash in swift 3

So I've recently had to convert my project to Swift 3, and while a bit of a pain, it has mostly gone ok. However I am now down to one final set of errors that I can't work out!
Im using
self.convert(point, to: v)
to convert a point between views, but Xcode keeps giving me build errors saying that
'convert(_:to:)' is only available on iOS 8.0 or newer
This seems to be due to the method definition
public protocol UICoordinateSpace : NSObjectProtocol {
#available(iOS 8.0, *)
public func convert(_ point: CGPoint, to coordinateSpace: UICoordinateSpace) -> CGPoint
...
}
clashing with the normal UIView one
extension UIView {
...
open func convert(_ point: CGPoint, to view: UIView?) -> CGPoint
...
}
I can't for the life of me work out how to resolve this! Has anyone come across this or have any idea how to fix it?!
You can manually set the iOS Deployment Target to 7.0, and Xcode builds the app as targeted to 7.0. (I don't know apps built with that manner can be approved for App Store.)
So, you can test that behaviour with your Xcode 8.
And you can silence Xcode with this:
self.convert(point, to: v as UIView?)
With new renaming rule of Swift 3, some imported methods may have the same signature in Swift, and I'm afraid some of them may not have this sort of workaround, and may not be solved by changing target version.
Ok, if anyone else has this issue, turns out it's because pre-iOS 8 is no longer supported. If you update your project to recommended settings, it will set your minimum iOS version to 8 and everything will be happy :)

iOS target app for both iOS 6 and iOS 7 with ECSlidingViewController component

I am using ECSlidingViewController for "hamburger" menu. I am using SDK 7.0 but I changed deployment target to iOS 6.1 and now I am trying my app with older iOS then 7. The problem is with setEdgesForExtendedLayout. There is older ECSlidingViewController for older system versions. So my question is how can I change to use old version for iOS 6.1 and older and newer version for iOS 7.0 and newer. I include files from both ECSlidingViewController projects (not by cocoapods but if it is need then it's not problem to change it). I guess I need check for OS version and then change imports but I am not sure if it is enough and what's best name convention for both project. I guess they should be in different folders (like ECSlidingViewController and ECSlidingViewControllerOld) but class should be same name, is it right?
Edit: Example of code with edgesForExtendedLayout:
- (CGRect)underLeftViewCalculatedFrameForTopViewPosition:(ECSlidingViewControllerTopViewPosition)position {
CGRect frameFromDelegate = [self frameFromDelegateForViewController:self.underLeftViewController
topViewPosition:position];
if (!CGRectIsInfinite(frameFromDelegate)) return frameFromDelegate;
CGRect containerViewFrame = self.view.bounds;
if (!(self.underLeftViewController.edgesForExtendedLayout & UIRectEdgeTop)) {
CGFloat topLayoutGuideLength = [self.topLayoutGuide length];
containerViewFrame.origin.y = topLayoutGuideLength;
containerViewFrame.size.height -= topLayoutGuideLength;
}
if (!(self.underLeftViewController.edgesForExtendedLayout & UIRectEdgeBottom)) {
CGFloat bottomLayoutGuideLength = [self.bottomLayoutGuide length];
containerViewFrame.size.height -= bottomLayoutGuideLength;
}
if (!(self.underLeftViewController.edgesForExtendedLayout & UIRectEdgeRight)) {
containerViewFrame.size.width = self.anchorRightRevealAmount;
}
return containerViewFrame;
}
I am not a fan of including duplicate versions of libraries, as this creates a big problem with naming, and a lot of work to refactor all old classes to have some *-OLD suffix. Since you have access to the source, you can modify the newer version like so:
if(NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1)
{
[vc setEdgesForExtendedLayout:UIRectEdgeNone];
//Any other iOS7-specific code.
}
First, the easiest way to do it will be have the most recent version of component that support the lowest deployment target.
But if you really want to have different version for each iOS, I don't know better solution, than just rename all classes from, for example, older version (because there was only two classes) and manage creation of this controller programmatically, because there's no way to set different class for different iOS version in xib's or storyboard's. You need to wrap each and every call of this component with iOS version check ( How to check iOS version? ).
Imports and variables you can leave for each version of component without check.
The trick you mentioned failed in this case, because it works good for different architecture, because binaries for different architectures will be included in final app and that's named fat binary, but there's the same architecture for iOS6 and iOS7 ( only one new in iOS7 - arm64). So you can't wrap only includes with preprocessor macros and get fat binary with different code for each iOS version.
I hope you've understood something from my explanation.
It seems that the only thing preventing you from using the new version with iOS 6 is the lack of topLayoutGuide and bottomLayoutGuide, which if available would just return lengths of zero anyway.
Why not "backport" those methods in your controller?
- (id<UILayoutSupport>)topLayoutGuide
{
CustomLayoutGuide * guide = [CustomLayoutGuide new];
guide.length = SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0") ? super.topLayoutGuide.length : 0.0;
return guide;
}
And the helper class
#interface CustomLayoutGuide : NSObject <UILayoutSupport>
#property(nonatomic) CGFloat length;
#end
#implementation CustomLayoutGuide
#end
IMHO the easiest thing to do (and probably the best though) is to take the current ECSlidingViewController and integrate part of the old when edgesForExtendedLayout is not available (e.g. if ([self respondsToSelector:#selector(edgesForExtendedLayout)]) { /*...*/} ). This should be pretty easy and fast to do since the old ECSlidingViewController just consists of two files and these files are also present in the new ECSlidingViewController.
You could make your life even easier when doing this by using a diff tool to be sure of what you should add.

Resources