UIViewController reported as responding to addChildViewController: on iOS 4 - ios

Has anyone else encountered this? The following code reports "YES" when running on the iOS 4 simulator but according to the Apple docs the method addChildViewController is only available on iOS 5 and later. This doesn't seem like the correct behavior, is this a bug?
if([UIViewController instancesRespondToSelector:#selector(addChildViewController:)]) {
NSLog(#"YES");
} else {
NSLog(#"NO");
}

I think this is a bug. Calling the addChildViewController seems to run without any warning or error too.
I wrote the following viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
MyChildView *aChildViewController = [[MyChildView alloc] initWithNibName:#"MyChildView" bundle:nil];
// Do any additional setup after loading the view, typically from a nib.
SEL mySelector = #selector(addChildViewController:);
if([UIViewController instancesRespondToSelector:mySelector] == YES) {
NSLog(#"YES addChildViewController:");
[self addChildViewController:aChildViewController];
} else {
NSLog(#"NO addChildViewController:");
}
if([UIViewController instancesRespondToSelector:#selector(automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers)] == YES) {
NSLog(#"YES automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers");
} else {
NSLog(#"NO automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers");
}
}
In the iOS 4.3 Simulator I see following output. Both messages are restricted to IOS 5.0 and higher. It appears addChildViewController is responding in the 4.3 simulator incorrectly. I don't have 4.3 device to test on an actual device.
2011-11-18 09:55:12.161 testViewFunctionality[873:b303] YES addChildViewController:
2011-11-18 09:55:12.162 testViewFunctionality[873:b303] NO automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
In the iOS 5.0 Simulator both respond which is correct behavior.
2011-11-18 09:59:31.250 testViewFunctionality[932:f803] YES addChildViewController:
2011-11-18 09:59:31.252 testViewFunctionality[932:f803] YES automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
I'm using XCode 4.2 on Lion. When I look through UIViewController.h on the 4.3 Simulator's framework there is no mention of addChildViewController: or automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers but the only SDK included is 5.0.
I suppose that if you wanted to be cautious you could test the running iOS version on the running device. See How to check iOS version?

Yes, that is a bug and it will never be fixed. As a workaround, instead of checking for the availability of addChildViewController: method, you can check for removeFromParentViewController method. The latter is not available before iOS 5.0.

It's quite possible this method existed in previous version of iOS, but it just wasn't public yet. Apple usually prepends private methods with an underscore but it has been known to do this kind of thing before.

Related

Property 'keyWindow' not found on object of type 'UIWindowScene *'

When attempting to build a flutter app via xcode, this is an error I am getting, which a semantic issue and a part of "GoogleSignIn". The application was working fine until I implemented an alternative method for users to sign in (google sign in). I know that my implementation is correct because it works flawlessly on an Android emulator (Windows laptop), but has issues running on a Mac. Any ideas as to what might be the problem? Here is the code snippet to which the error points to:
// This method is exposed to the unit test.
- (nullable UIWindow *)keyWindow {
if (#available(iOS 15, *)) {
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
if ([scene isKindOfClass:[UIWindowScene class]] &&
scene.activationState == UISceneActivationStateForegroundActive) {
return ((UIWindowScene *)scene).keyWindow;
}
}
}

Possible to bring the app from background to foreground?

When running an XCT UI test it is possible to put the application under test in the background with:
XCUIDevice().pressButton(XCUIDeviceButton.Home)
It it possible in some way to bring the app back to foreground (active state) without relaunching the application?
Update for Xcode 9: Starting in Xcode 9, you can now simply call activate() on any XCUIApplication.
let myApp = XCUIApplication()
myApp.activate() // bring to foreground
https://developer.apple.com/documentation/xctest/xcuiapplication/2873317-activate
Yes, it is. But, you'll need XCUIElement's private headers (which are available via header dump from Facebook here). In order to foreground the app, you need to call resolve which I believe resolves the element's query (which for applications means foregrounding the app).
For Swift, you'll have to import the XCUIElement.h into your bridging header. For Objective-C you'll just need to import XCUIElement.h.
With the app backgrounded:
Swift:
XCUIApplication().resolve()
Objective-C
[[XCUIApplication new] resolve];
If this is the only functionality you need, you could just write a quick ObjC category.
#interface XCUIElement (Tests)
- (void) resolve;
#end
If you need to launch / resolve another app. Facebook has an example of that here by going through the Springboard.
As of Xcode 8.3 and iOS 10.3, you can accomplish this with Siri:
XCUIDevice.shared().press(XCUIDeviceButton.home)
XCUIDevice.shared().siriService.activate(voiceRecognitionText: "Open {appName}")
Include #available(iOS 10.3, *) at the top of your test suite file and you should be good to go!
This is what I have in my XCUITest and it works like a charm (xcode 10.1 and test device is iPhone X 11.0)
func testWhatever() {
// You test steps go here until you need the background foreground to run
XCUIDevice.shared.press(XCUIDevice.Button.home) // To background the app
XCUIApplication().activate() // To bring the app back
// You test continues after background foreground has been done.
}
If somebody needs just move app back from background i have written (based on answer above) category that really works(great thanks to pointing to FB git)
#implementation XCUIApplication(SpringBoard)
+ (instancetype)springBoard
{
XCUIApplication * springboard = [[XCUIApplication alloc] performSelector:#selector(initPrivateWithPath:bundleID:)
withObject:nil
withObject:#"com.apple.springboard"];
[springboard performSelector:#selector(resolve) ];
return springboard;
}
- (void)tapApplicationWithIdentifier:(NSString *)identifier
{
XCUIElement *appElement = [[self descendantsMatchingType:XCUIElementTypeAny]
elementMatchingPredicate:[NSPredicate predicateWithFormat:#"identifier = %#", identifier]
];
[appElement tap];
}
#end
For Swift, you need to declare the XCUIApplication private methods interface in Bridging-Header.h like this:
#interface XCUIApplication (Private)
- (id)initPrivateWithPath:(NSString *)path bundleID:(NSString *)bundleID;
- (void)resolve;
#end
Then call resolve() in your test cases to bring the app back:
XCUIApplication().resolve()
Since Xcode 13 we got several errors that the app was not in foreground state after returning to the app.
applying this code to our "goToSpringboardAndBack()" works
XCUIDevice.shared.press(XCUIDevice.Button.home)
if XCUIApplication().wait(for: .runningBackground, timeout: 5.0) {
XCUIApplication().activate()
}
_ = XCUIApplication().wait(for: .runningForeground, timeout: 5.0)
´´´

ABAddressBook to CNContact App Transition

I am working on an app that is close to launch but uses the ABAddressBook framework. With the deprecation of ABAddressBook in iOS9, do I need to check the user's iOS version, and use ABAddressBook for pre iOS9 users, and CNContact for iOS9 users?
How is everyone else handling this? I haven't been in a situation like this before.
I have also been dealing-with and researching this issue, what I've opted to do is as you suggest; check the users iOS version doing something like the following:
NSString *version = [[UIDevice currentDevice] systemVersion];
BOOL isVersion8 = [version hasPrefix:#"8."];
BOOL isVersion7 = [version hasPrefix:#"7."];
//...
...continuing based on the versions you've decided to support for your app.
Then I do a check to either use the Addressbook framework for earlier than iOS 9, and Contacts framework for iOS 9 and beyond.
if(isVersion7 || isVersion8){
//Use AddressBook
}
else{
//Use Contacts
}
That's the best way I could think to deal with this deprecation business...
Deprecated doesn't mean removed. Just make linking to both frameworks as optional and start to design data workflow that can handle both frameworks. Also please mind that CNContact is new and full of bugs.
Once you think your app is refactored and iOS evolved to 9.1 give it a green light
How to know if system supports functionality
1) Check if the class exists
if(NSClassFromString(#"CNContact")) {
// Do something
}
For weakly linked classes, it is safe to message the class, directly. Notably, this works for frameworks that aren't explicitly linked as "Required". For missing classes, the expression evaluates to nil.
2)
#ifned NSFoundationVersionNumber_iOS_9
#def NSFoundationVersionNumber_iOS_9 NUMBER
#endif
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9) {
// Use address book
} else {
// Use contact framework
}
Run the app in simulator to find the NSFoundationVersionNumber constant
if #available(iOS 9, *)
{
// iOS 9 - CNContact
}
else
{
// iOS 8 - ABAddressBook
}
This is the right way to check.

iOS Today Extension created as .app rather than .appex

I'm trying to add a Today Extension to a project I've been working on for quite some time. In fact the app is in the AppStore already and I'm looking to enhance it with a Today Extension.
The problem is that the Extension won't launch at all. Not on the device nor on the simulator.
EDIT: just skip the next sections and read on at the last EDIT as I think I found the problem. I just not sure how to fix it.
I've done a test project following a tutorial and it works just fine. The environment seem(!) to be identical. Xcode 6.1.1, iOS 8.1 on the device and simulator.
My project is Objective-C based. For the Extension I’ve tried both Objective-C and Swift targets. On both occasions all three (four with obj-C) files were created as expected (storyboard, viewController and PLIST).
Having done nothing more (as with the example project) I'm trying to launch the widget with the widget scheme selected. With the test projects the widget would launch while it won't with the actual project.
I put a println()/NSLog in the viewDidLoad of the widgets viewController to see if anything happens but nothing.
Happy to provide code or settings but at this pointing time I've no idea where to start.
I just realised that with the test project the today view would launch/appear automatically when the widget gets run from Xcode. With my actual project I'm just getting the HomeScreen and have to pull down the Today view myself. So, really nothing at all happens regarding the widget while everything looks identical compared to the test project.
Any help is appreciated.
EDIT: Here is something I came across which might constitute the problem. The widget never gets launched really and gets stuck at ´Waiting to Attach´ in Xcode's Debug navigator. While other seemed to have had the same problem all potential solutions I found so far did't work for me.
EDIT: I noticed that when I add a Today widget as a target the binary is named .app. All test projects I did the binary gets created as .appex. All the information on the web suggests that it should be named .appex really. Where does this come from and how do I alter this?
I had the same problem.
The following steps helped:
selected target Today Extortion -> Build Settings -> line Wrapper Extension add (change) value to appex
See:
Same problem happened today when I created a Notification Content extension in an old project.(2016, Xcode8 iOS10)
Finally I found the cause:
"Wrapper Extension" in Build Settings of the project was “app”, and when the new target of extension was created, "Wrapper Extension” inherited from the project settings as “app”.
Clearing the project setting before adding an extention target will make Xcode creat an extention as “appex” automatically.
I am herewith sharing the step and source code.
Step 1:- App extension must have a containing app - you can't just create an app extension to be downloaded from the store, first create a regular app to contain the app extension. For the sake of this demonstration just create a new single view project and leave it untouched. Go to File-> New-> Project and select Single view application under iOS -> Applications call it 'ExtendableApp'.
Step 2:- If you want to create your custom experience simply set your ExtensionViewController to inherit from UIViewController, Once your extension is activated all the regular viewDidLoad, viewDidAppear, etc will be called.
Step 3:- In your controller storyboard create outlets for button, I am herewith describing 3 buttons.
Step 4:- In ExtensionViewController.m write
- (void)viewDidLoad {
[super viewDidLoad];
self.preferredContentSize = CGSizeMake(self.view.frame.size.width, 60.0f);
// Do any additional setup after loading the view from its nib.
}
Step 5:- I am assuming that you have set the outlets and IB Action of your buttons in extension storyboard
- (IBAction) mActionButtonTapped :(UIButton *) sender {
switch (sender.tag) {
case 0: {
NSURL *url = [NSURL URLWithString:#"IDENTIFIER_1://"];
[self.extensionContext openURL:url completionHandler:nil];
}
break;
case 1: {
NSURL *url = [NSURL URLWithString:#"IDENTIFIER_2://"];
[self.extensionContext openURL:url completionHandler:nil];
}
break;
case 2: {
NSURL *url = [NSURL URLWithString:#"IDENTIFIER_3://"];
[self.extensionContext openURL:url completionHandler:nil];
}
break;
default:
break;
}
}
Step 6:- In your project write these code in appDelete.m
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
[self appExtensionCallBack:url.absoluteString];
return YES;
}
- (void) appExtensionCallBack :(NSString *)urlString {
if ([urlString isEqualToString:#"IDENTIFIER_1://"]) {
[self.tabBarController setSelectedIndex:0];
} else if ([urlString isEqualToString:#"IDENTIFIER_2://"]) {
[self.tabBarController setSelectedIndex:1];
} else if ([urlString isEqualToString:#"IDENTIFIER_3://"]) {
[self.tabBarController setSelectedIndex:2];
}
}
Note :- I am using Tab Bar Controller in my project, You can give own respected controller.
- (void) moveToControllerScene {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:STORY_BOARD_IDENTIFIER bundle:nil];
YOUR_CONTROLLER_OBJECT *obj = [storyboard instantiateViewControllerWithIdentifier:#"YOUR_CONTROLLER_OBJECT"];
[navController pushViewController:obj animated:YES];
}
Step 7:- For testing the Extension in real device you have to make a separate App ID and Provisioning profile. Delete appropriate provisioning profile in extension and ur project.

weak vs strong pointers behaving slightly differently in xcode 5

i have some code that was working happily in xcode 4.6. since i upgraded to xcode 5 one section was not working. it's not erroring. very oddly the exact same code with no modifications DOES work in the simulators, but does NOT work on devices. if i compile the same coe in xcode 4.6 it DOES work on devices as well as simulators.
it uses tony millions reachability class.
i have tracked down the issue but becasue itworks on simulators in xcode 5, i dont understand.
basically on a button click i check the reachability.
i have a weak pointer to a reachability object, which i setup on the button click - snippet below
#interface settingsViewController ()
#property (weak,nonatomic) Reachability *reachable;
#end
....
//called on click
/ Checks if we have an internet connection or not
- (void)testInternetConnection
{
self.reachable= [Reachability reachabilityWithHostname:#"www.google.com"];
if (self.reachable)
{
NSLog(#"reachability created");
}
else
{
NSLog(#"NO OBJECT");
}
//do more stuff.....
}
basically without changing ANYTHING in the simulators the debug console prints "reachability created" but on ANY device (iPhone/ipad, IOS6/IOS7) the debug console prints "NO OBJECT"
tony milions code to create the object using reachabilityWithHostname is below
#pragma mark - class constructor methods
+(Reachability*)reachabilityWithHostname:(NSString*)hostname
{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
NSLog(#"init1");
if (ref)
{
NSLog(#"init2");
id reachability = [[self alloc] initWithReachabilityRef:ref];
#if __has_feature(objc_arc)
NSLog(#"init with arc");
return reachability;
#else
NSLog(#"init no arc");
return [reachability autorelease];
#endif
}
NSLog(#"cannot init");
return nil;
}
and on both the simulator and device the debug console prints "init with arc" as expected.
so basically it creates the object OK, but as soon as i test it on the simulator it is valid, but on the device it is null. that bit i dont understand.
BUT if i change the reachability object to be a strong pointer, the simulator continues to work AND the device works as well - in that when i test the reachability object, it is set and the debug console prints "reachability created"
i dont understand how the object can be null as soon as it's created ONLY on the device with a weak pointer... surely
a) the simulator and device should behave the same
b) i have only just created the object the line before - how has it become null on the device if i'm using weak pointer?/
thanks in advance!
The code you posted should never work.
The fact that it does work on simulators is an artifact of the imperfect nature of the simulator, not a reflection that it SHOULD work.
The compiler should be giving you a warning that creating an object and saving it to a weak pointer, it will be released immediately. That's how ARC works.
Use a local strong variable. As soon as the strong variable goes out of scope, it will be up to the caller to decide if it should retain a strong reference to the object.

Resources