I would like to port an iOS app to macOS using Catalyst.
The app is an audio host for the AUv3 plugins.
The problem is that I can't get the plugin icon using the AudioComponentGetIcon API since is unavailable on macOS.
How can I get the plugin icon?
while (true) {
comp = AudioComponentFindNext(comp, &rau->_desc);
if (comp == NULL) break;
AudioComponentDescription desc = { 0, 0, 0, 0, 0 };
if (AudioComponentGetDescription(comp, &desc) != noErr) continue;
#if !TARGET_OS_MACCATALYST
rau->_image = AudioComponentGetIcon(comp, 76);
rau->_lastActiveTime = AudioComponentGetLastActiveTime(comp);
#else
#warning CATALYST WHAT I SHOULD DO here?
//rau->_image = AudioComponentGetIcon(comp);
#endif
if (rau->_image == nil) {
rau->_image = [UIImage imageNamed:DEFAULT_AU_IMAGE];
}
}
Apple's official answer: AudioComponentGetIcon was only ever supported on iOS for Inter-App Audio, not for audio unit extensions. Since Inter-App Audio is now deprecated on iOS, the functionality is not available in Catalyst, including AudioComponentGetIcon.
So, yes, in a Mac Catalyst app, it is not possible to get the AU icon.
In a macOS app (non-Catalyst), the AudioComponentGetIcon function is still available, but that doesn’t provide a solution to a Catalyst app. There is no “bridging” mechanism that allows your Catalyst app to cross over to the macOS function.
I’ve also figure out this workaround but __comp.icon and __comp.iconURL still unavailable in Catalyst.
AVAudioUnitComponentManager* av = [AVAudioUnitComponentManager sharedAudioUnitComponentManager];
NSArray<AVAudioUnitComponent *>* comps = [av componentsMatchingDescription:desc];
AVAudioUnitComponent* __comp = [comps objectAtIndex:0];
NSImage *iconIMG = __comp.icon; //not available on Catalyst
NSURL *iconURL = __comp.iconURL; //not available on Catalyst
Related
There seems to be a bug in iOS 13 (Safari and WkWebView) that makes iOS use the device language voice and not find a suitable voice by looking at the 'lang' provided in the SpeechSynthesisUtterance.
I worked around the issue by setting a suitable voice myself.
This is not needed in other browsers/platforms (eg macOS Safari, iOS < 13, Chrome etc.)
this._getUtteranceRate().then((rate) => {
let utterance = new SpeechSynthesisUtterance(words);
utterance.rate = rate;
utterance.lang = 'sv-SE';
utterance.voice = this.voice; //IOS13 fix
window.speechSynthesis.speak(utterance);
});
window.speechSynthesis.onvoiceschanged = () => {
this.setVoice();
}
setVoice() {
this.voice = window.speechSynthesis.getVoices().find((voice) => {
return voice.lang === 'sv-SE';
});
}
It seems one needs to set the voice explicitly on the SpeechSynthesisUtterance for iOS13, as the locale is not used to find the voice.
I want to to show message in my application when haptic feedback is disabled in phone settings. How to detect that haptic feedback is disabled in device settings?
It's kludgy, but might this work?
- (BOOL)isHapticFeedbackDisabled {
BOOL result = NO;
UISelectionFeedbackGenerator *feedbackGenerator = [[UISelectionFeedbackGenerator alloc] init];
[feedbackGenerator prepare];
if ([feedbackGenerator.description containsString:#"prepared=0"]) result = YES;
feedbackGenerator = nil;
return result;
}
There is no way to check Haptic Feedback is enabled/disabled but there is private int _feedbackSupportLevel in UIKit for checking if device supports it:
func logFeedbackSupported() {
let supportLevel = UIDevice.current.value(forKey: "_feedbackSupportLevel")
print(supportLevel ?? "")
}
0: Not available,
1: First generation available (< iPhone 7),
2: Second generation available.
I advise you not to use Apples private APIs because:
The API could be changed in any version without you knowing about it.
Apple is parsing your app code to find out if you're using private API so be aware. Your app could be rejected.
#available does not seem to work when differentiating between watchOS and iOS.
Here is an example of code shared between iOS & watchOS:
lazy var session: WCSession = {
let session = WCSession.defaultSession()
session.delegate = self
return session
}()
...
if #available(iOS 9.0, *) {
guard session.paired else { throw WatchBridgeError.NotPaired } // paired is not available
guard session.watchAppInstalled else { throw WatchBridgeError.NoWatchApp } // watchAppInstalled is not available
}
guard session.reachable else { throw WatchBridgeError.NoConnection }
Seems that it just defaults to WatchOS and the #available is not considered by the compiler.
Am I misusing this API or is there any other way to differentiate in code between iOS and WatchOS?
Update: Seems like I was misusing the API as mentioned by BPCorp
Using Tali's solution for above code works:
#if os(iOS)
guard session.paired else { throw WatchBridgeError.NotPaired }
guard session.watchAppInstalled else { throw WatchBridgeError.NoWatchApp }
#endif
guard session.reachable else { throw WatchBridgeError.NoConnection }
Unfortunately there is no #if os(watchOS) .. as of Xcode 7 GM
Edit: Not sure when it was added but you can now do #if os(watchOS) on Xcode 7.2
If you want to execute that code only on iOS, then use #if os(iOS) instead of the if #available(iOS ...).
This way, you are not using a dynamic check for the version of your operating system, but are compiling a different code for one OS or the other.
In the Apple dev guide, it is said that the star, * (which is required) means that it will execute the if body for OSes not specified but listed in the minimum deployment target specified by your target.
So, if your target specifies iOS and watchOS, your statement if #available(iOS 9.0, *) means that the ifbody is available for iOS 9 and later and any watchOS version.
Also, be careful if you want to use what's described in the chapter "Build Configurations" in this Apple guide. It is used to conditionally compile your code based on the operating system. This is not dynamic at runtime.
With the GM version of Xcode7 I think they fixed that issue. For me :
if #available(watchOS 2,*) {
// Only if using WatchOS 2 or higher
}
is working fine in GM version.
Time have passed since the question. If somebody still looking for the answer, need to say that
#if os(watchOS)
is now available in Xcode 13 and later.
I would like to detect if the user has enabled Reduce Transparency. It's simple you just call the func UIAccessibilityIsReduceMotionEnabled() and it returns a Bool. But my app targets iOS 7 and 8 and this function isn't available on iOS 7.
In Objective-C, this is how I checked to see if that function exists:
if (UIAccessibilityIsReduceMotionEnabled != NULL) { }
In Swift, I can't figure out how to check if it exists or not. According to this answer, you can simply use optional chaining and if it's nil then it doesn't exist, but that is restricted to Obj-C protocols apparently. Xcode 6.1 doesn't like this:
let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled?()
It wants you to remove the ?. And of course if you do so it will crash on iOS 7 because that function doesn't exist.
What is the proper way to check if these types of functions exist?
A proper check for availability has been added in Swift 2. This is recommended over other options mentioned here.
var shouldApplyMotionEffects = true
if #available(iOS 8.0, *) {
shouldApplyMotionEffects = !UIAccessibilityIsReduceMotionEnabled()
}
If you're okay with being a little bit cheeky, you can always open the UIKit binary using the library loader and see if it can resolve the symbol:
let uikitbundle = NSBundle(forClass: UIView.self)
let uikit = dlopen(uikitbundle.executablePath!, RTLD_LAZY)
let handle = dlsym(uikit, "UIAccessibilityIsReduceMotionEnabled")
if handle == nil {
println("Not available!")
} else {
println("Available!")
}
The dlopen and dlsym calls can be kinda expensive though so I would recommend keeping the dlopen handle open for the life of the application and storing somewhere the result of trying to dlsym. If you don't, make sure you dlclose it.
As far as I know this is AppStore safe, since UIAccessibilityIsReduceMotionEnabled is a public API.
You could check to see if you're running in iOS 8 or higher --
var reduceMotionEnabled = false
if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 8, minorVersion: 0, patchVersion: 0)) {
reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}
I don't think there's another way to tell. So in theory, if you were able to check, trying to access the function name without the () would give you nil in iOS 7 and the () -> Bool function in iOS 8. However, in order for that to happen, UIAccessibilityIsReduceMotionEnabled would need to be defined as (() -> Bool)?, which it isn't. Testing it out yields a function instance in both versions of iOS that crashes if called in iOS 7:
let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled
// reduceMotionDetectionIsAvailable is now a () -> Bool
reduceMotionDetectionIsAvailable()
// crashes in iOS7, fine in iOS8
The only way I can see to do it without testing the version is simply to define your own C function to check in your bridging header file, and call that:
// ObjC
static inline BOOL reduceMotionDetectionIsAvailable() {
return (UIAccessibilityIsReduceMotionEnabled != NULL);
}
// Swift
var reduceMotionEnabled = false
if reduceMotionDetectionIsAvailable() {
reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}
From the Apple Developer docs (Using Swift with Cocoa and Objective-C (Swift 3) > Interoperability > Adopting Cocoa Design Patterns > API Availability):
Swift code can use the availability of APIs as a condition at
run-time. Availability checks can be used in place of a condition in a
control flow statement, such as an if, guard, or while
statement.
Taking the previous example, you can check availability in an if
statement to call requestWhenInUseAuthorization() only if the method
is available at runtime:
let locationManager = CLLocationManager()
if #available(iOS 8.0, macOS 10.10, *) {
locationManager.requestWhenInUseAuthorization()
}
Alternatively, you can check availability in a guard statement,
which exits out of scope unless the current target satisfies the
specified requirements. This approach simplifies the logic of handling
different platform capabilities.
let locationManager = CLLocationManager()
guard #available(iOS 8.0, macOS 10.10, *) else { return }
locationManager.requestWhenInUseAuthorization()
Each platform argument consists of one of platform names listed below,
followed by corresponding version number. The last argument is an
asterisk (*), which is used to handle potential future platforms.
Platform Names:
iOS
iOSApplicationExtension
macOS
macOSApplicationExtension
watchOS
watchOSApplicationExtension
tvOS
tvOSApplicationExtension
This question already has answers here:
How can I programmatically determine if my app is running in the iphone simulator?
(21 answers)
Closed 9 years ago.
I have some logic in my iOS application that I would like to execute differently when testing on an the iPhone simulator vs when its running on a live device.
Is there any way to determine in objective C whether the logic is being executed on one or the other?
Currently, I comment out some code before I deploy to my physical iPhone. Not convenient.
The reason behind the (slightly) different execution paths btw, is that my application utilizes data that is time/date dependent. On the simulator i have a static data set loaded so my testing takes that into account (i.e doesnt use current systems dates etc).
On the live device, the data is always fresh so no such issues.
It really should be known at compile time, as per TARGET_IPHONE_SIMULATOR macro. If you need to do a runtime check:
bool is_simulator()
{
NSString *model = [[UIDevice currentDevice] model];
return [model isEqualToString:#"iPhone Simulator"];
}
or without using objective C you could perhaps use sysctl as follows:
static int32_t sysctl_int32(const char* name)
{
int32_t val = 0;
size_t size = sizeof(val);
sysctlbyname(name, &val, &size, NULL, 0);
return val;
}
bool is_simulator()
{
int32_t cpu_type = sysctl_int32("hw.cputype");
return (cpu_type == CPU_TYPE_I386 || cpu_type == CPU_TYPE_X86_64)
}
Try
if (TARGET_IPHONE_SIMULATOR){
//Running on simulator
}else{
//Real one
}
Use
#if TARGET_IPHONE_SIMULATOR
// Using Simulator
#else
// Using device