Best iphone questions in June 2012

Simulating low battery for iPhones

18 votes

I am working on a mobile game, which appearantly crashes when the Low Battery alert is displayed. It works fine on low memory, incoming calls and other messages.

Its a pain to test and debug this, since I can find no terminal or iPhone simulator way of simulating this situation, so I have to charge my phone up a little bit, launch the app, wait for it to drain its power, and start all over again.

Does anyone know of a way to produce this error in a realistic way? Hopefully something that isn't too stressful on my iPhone battery.

Unfortunately, there is no good way to simulate a low-battery environment. You actually will most likely need to physically charge your device's battery until it is just above the "low battery" state and then debug your application.

To address what others have said:

  1. There is no way to simulate low battery notifications. The project that @Bo. provided does nothing more than schedule random UILocalNotifications. It isn't all that much different than just showing a UIAlertView in your app.
  2. You could try what @Andrew R. said and use the private methods found in the UIDevice header. But it is doubtful that you will exactly mimic the effects of a real low-battery environment.

Although it is a pain to have to wait for you device to actually hit the low-battery state, you could add some battery-draining code to your app to assist you. For example, using the GPS might drain the battery a bit quicker.

Good luck.

How to set UITextView's content inset like Notes App

12 votes

I am working in an app in which I need to give feature like Notes App in iphone. as shown in first screen shot , initially , notes leaves a tab before the content starts, I also wanted to do the same and for that when I set Left Content inset (of UITextView) by 25 , it shows like in screenshot 2, here you may see the image also gets shifted. I have set image as background. I don't know how to solve this problem.

I also tried by adding image as subview of UITextview but it won't repeat the lines, while scrolling (image of lines) like notes app.

Notes App output I get

I'm setting the background of Textview by following code.

[textView setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"line_image.png"]]];

Please tell me if I am going wrong or any extra effort needed to get desired output.

Thanks

@Dinesh gave nice solution but it doesn't sound to be generic as I want to use image of lines instead of drawing them. Image has some special effects that can not be achieved by drawing. So to solve it I created a table view below the textview keeping textview's background transparent. Now just added the image of line to my tableview's custom cell and set content offset of UItableview same as of the scrollview (subview of text view ,refering to the answer of @Vladimir ) .

And used the following code to scroll my tableview with scrolling the textview and got the desired output.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{           
    tableView.contentOffset =scrollView.contentOffset; 
}

Keeping my tableview's number of rows at a very large number.

PS: instead of setting the content inset of textview, i set its frame's X position and decreased the width relaively.

Detect which version of an app is installed in iOS?

12 votes

I understand it's possible to detecting programatically whether an app is installed on iphone . I'm wondering if it's possible to detect what version of the other app is installed?

My app has a dependency on the Facebook native client, but behaves pretty badly if the phone has an older version of the Facebook app installed. So I'd like to be able to detect that and warn users.

-- UPDATE --

It's being implied in the comments that I can prevent users from installing my app in the first place if the appropriate version of the dependent app is not present. That would be a great solution too. If you know how I can specify a dependency on another app's version number, please explain that.

Unfortunately, you can not read your iOS device settings programmatically to get the native app Facebook version (i.e. via Settings, Facebook, Version).

Nevertheless, you might try to experiment with the custom URL schemes for Facebook as you noted in your own question.

It seems the different versions of the native Facebook application either support/does not support its own custom URL schemes.

As noted here from version 3.4, you can:

fb://places

From version 4.0, as noted here, you can:

fb://place/(fbid)

etc.

"Testing Class Equality" in Objective-c

11 votes

I am having problem understanding this part of "Testing Class Equality" which is defined in apple guide.

In a dynamically-created subclass, the class method is typically overridden such that the subclass masquerades as the class it replaces. When testing for class equality, you should therefore compare the values returned by the class method rather than those returned by lower-level functions. Put in terms of API, the following inequalities pertain for dynamic subclasses:

[object class] != object_getClass(object) != *((Class*)object)

You should therefore test two classes for equality as follows:

if ([objectA class] == [objectB class]) { //...

There are situations in which people add new classes at runtime. One example is Key Value Observing: when you observe an object the Foundation framework creates a new subclass of the observed object's class. This dynamic class behaves in the same way as its superclass, but adds KVO notifications to all of its mutator methods.

The passage you quoted says that the Objective-C runtime can tell this new class apart from the original class. However, because it's just an implementation detail of the way KVO is built, you shouldn't know or care about it. The developers therefore overrode the -class method of their new class, to pretend that objects were still members of the original class.

If you want to check whether two objects are of the same class, you must therefore compare the results of their -class methods (which take tricks like KVO into account), instead of using runtime functions.

Here's an example:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSObject *observer = [NSObject new];
        NSObject *model = [NSObject new];

        [model addObserver: observer forKeyPath: @"count" options: 0 context: NULL];

        //using -class methods:
        NSLog(@"model is a %@, observer is a %@", [model class], [observer class]);

        //casting to Class:
        NSLog(@"model is a %@, observer is a %@", *(Class*)model, *(Class*)observer);

        //using the runtime:
        NSLog(@"model is a %@, observer is a %@", object_getClass(model), object_getClass(observer));

        [model removeObserver: observer forKeyPath: @"count" context: NULL];
        [model release];
        [observer release];
    }
    return 0;
}

You see that all I'm doing is creating two objects, telling one of them to observe the other, then finding out what their classes are. Here are the results:

2012-06-08 08:37:26.904 Untitled 2[896:707] model is a NSObject, observer is a NSObject

2012-06-08 08:37:26.907 Untitled 2[896:707] model is a NSKVONotifying_NSObject, observer is a NSObject

2012-06-08 08:37:26.907 Untitled 2[896:707] model is a NSKVONotifying_NSObject, observer is a NSObject

So as the documentation suggests, it's only the first case (where we compare -class) that does anything the application code could reasonably expect. The other two ways of finding out the class - asking the runtime, and casting the object pointer to a Class * - both give away implementation details about how KVO has changed the class from underneath us, and mean that the class comparison now won't show that the classes are equal.

Because other answers and comments are referring to -isMemberOfClass: and -isKindOfClass:, I'll cover those points too:

  • -isKindOfClass: is not a test for class equality. [object isKindOfClass: aClass] is true if object is an instance of aClass or any of its subclasses. Because the passage you've quoted is about class equality, -isKindOfClass: is not relevant here. That said, it's most often the test that you want to be doing in application code. It's more common to care about the answer to "can I use this object as a Foo?" than "is this object an instance exactly of Foo?".

  • -isMemberOfClass: is a test for class equality: [object isMemberOfClass: aClass] is only true if object is an instance of aClass. This test is done using the result of the -class method, which means that in this example model will test positive for [model isMemberOfClass: [NSObject class]].

UIScrollview setContentOffset with non linear animation ?

11 votes

Hello stackoverflow !

I am trying to reproduce the smooth animation of a scrollview with paging enabled when you actually scroll to the next page. It seems to be UIViewAnimationCurveEaseInOut, but I need to have a "next page" button and trig the scroll programmatically.

Here is my code :

-(void) scrollToPage:(int)page
{
    UIScrollView *scrollView = contentView;
    CGPoint offset = CGPointMake(scrollView.bounds.size.width * page, scrollView.contentOffset.y);
    [scrollView setContentOffset:offset animated: YES];     
    [self pageControlUpdate];
}

-(void) scrollToNextPage 
{
    [self scrollToPage:(pageControl.currentPage + 1)];
}

I cannot manage to reproduce the smoothness of UIViewAnimationCurveEaseInOut, either with setContentOffset, or with scrollRectToVisible... it goes to the next page with an ugly linear animation

I even tried to animate it manually :

[UIView animateWithDuration:.5 delay:0 options:UIViewAnimationCurveEaseInOut animations:^{
    scrollView.contentOffset = offset;
} completion:^(BOOL finished) {    } ];

where am I wrong ?

Many thanks in advance, I am stuck on this for several days ...

Use this:

[scrollView _setContentOffset:offset duration:(double)duration animationCurve: UIViewAnimationCurveEaseInOut];

Although this method is undocumented it works (I have checked this with UITableView on iPhone 4 with iOS 5.1.1 on board)

Should I be worried about rumors that Apple will stop using Google Maps in iOS6?

9 votes

Almost every major news media outlet is reporting that Apple will stop using Google Maps services in iOS6. I don't think anyone really knows what exactly the changes are, when they will be rolled out, how Apple will implement them and if Google or Apple will start charging money for their services. As a result of these uncertainties, I am having trouble making a sound decision on whether I should or should not release a new public bus tracker app that I have been working on for over a year now. I have had the initial hard deadline set for June 15, 2012. I know that the best thing to do is to wait and see what the changes will be and how Apple will implement them. But I am a bit impatient and stressed out about pushing the release date as any further delay will affect many of my other plans in a very significant way.

More information on the app:

It provides real time public transit information based on information it obtains from a third-party transit agency's API. It heavily depends on the iOS MapKit framework and iOS location services to display current geolocation of buses on a given route in realtime and to provide transit information between two locations. According to Apple, the iOS MapKit framework uses Google services to provide map data. Some features invoke the Google Maps app to provide transit planning and turn-by-turn directions to nearest stops.

My question:

If the rumors turn out to be true, do you think Apple will most certainly implement any changes without affecting apps that already use the current MapKit framework? What kinds of potential problems should I be prepared for?

from the experience i have with SDKs i can tell you that no, you will not need to worry, for a couple of reasons

  • apple is well known to maintaing backward compatible updates
  • the map kit api interface is encapsulated and it should not change at all
  • even if the techniques of achieving maps and routes changes, apple will still keep the same interface to access these functionalities
  • most of low level functionalities of any SDK are encapsulated, they public interfaces (that we developers have access to) are stable and should not change
  • a break in these interfaces must be very difficult to even think about
  • Change the lower level of implementation will not change the public interface of the mapkit api
  • even if the interface changed, apple will still leave the old interface and add deprecation metadata to them, these interfaces will still work, other interfaces maybe added.

Your only potential problems that you will need to think about are when you are going to update your app, and they would be on wether to implement the new functionalities that the new mapkit may have provided or not

issue in drawing line using core graphics : bubbles are shown

9 votes

Using core graphics , I am trying to draw a line, in that when I try to add shadow is creates lin and bubbles inside the line, see the image, I am not able to solve the issue. please find my code below

-(void)drawRect:(CGRect)rect
{

    [curImage drawAtPoint:CGPointMake(0, 0)];
    CGPoint mid1 = midPoint(previousPoint1, previousPoint2); 
    CGPoint mid2 = midPoint(currentPoint, previousPoint1);

    CGContextRef context = UIGraphicsGetCurrentContext(); 

    [self.layer renderInContext:context];

    CGContextMoveToPoint(context, mid1.x, mid1.y);
    CGContextAddQuadCurveToPoint(context, previousPoint1.x, previousPoint1.y, mid2.x, mid2.y); 
    CGContextSetLineCap(context, kCGLineCapRound);
    CGContextSetLineWidth(context, self.lineWidth);
    CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);

    CGContextSetShadow(context, CGSizeMake(-16.00, -5.0f), 5.0f);

    CGContextStrokePath(context);

    [super drawRect:rect];

    [curImage release];

}

Below is the touchesMoved method.

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{

    UITouch *touch  = [touches anyObject];

    previousPoint2  = previousPoint1;
    previousPoint1  = [touch previousLocationInView:self];
    currentPoint    = [touch locationInView:self];

    // calculate mid point
    CGPoint mid1    = midPoint(previousPoint1, previousPoint2); 
    CGPoint mid2    = midPoint(currentPoint, previousPoint1);

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, mid1.x, mid1.y);
    CGPathAddQuadCurveToPoint(path, NULL, previousPoint1.x, previousPoint1.y, mid2.x, mid2.y);
    CGRect bounds = CGPathGetBoundingBox(path);
    CGPathRelease(path);

    CGRect drawBox = bounds;

    //Pad our values so the bounding box respects our line width
    drawBox.origin.x        -= self.lineWidth * 2;
    drawBox.origin.y        -= self.lineWidth * 2;
    drawBox.size.width      += self.lineWidth * 4;
    drawBox.size.height     += self.lineWidth * 4;

    UIGraphicsBeginImageContext(drawBox.size);
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];
    curImage = UIGraphicsGetImageFromCurrentImageContext();
    [curImage retain];
    UIGraphicsEndImageContext();
   [self setNeedsDisplayInRect:drawBox];

}

CGPoint midPoint(CGPoint p1, CGPoint p2)
{
    return CGPointMake((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5);
}


-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{

    UITouch *touch = [touches anyObject];

    previousPoint1 = [touch previousLocationInView:self];
    previousPoint2 = [touch previousLocationInView:self];
    currentPoint = [touch locationInView:self];

    [self touchesMoved:touches withEvent:event];
}

Here the issue arrives when I try to add the line

CGContextSetShadow(context, CGSizeMake(-16.00, -5.0f), 5.0f); 

Issue image

Please help me out from the issue, thanks in advance. please see editing question with methods .

It would appear you are drawing a series of segments as quad curves. The bubbles would be where the previous segment and the current segment overlap. Since your segment line is a solid red, you don't notice the overlap, but it shows up in the shadows as darker regions.

Try setting your stroke color to have and alpha of 0.5 (or some such translucency). I think (know) you will see the overlapping segments will also show a similar effect as the the shadow.

To solve it, you will want to draw the segment as a continuous path for each line. I'm guessing you are using touchesBegan:withEvent: / touchesMoved:withEvent / touchesEnded:withEvent: to obtain the points of the lines?

In that case, when touchesBegan:withEvent: is called, start your new path. During touchesMoved:withEvent render the new path over the current image. Commit the path to a UIImage in touchesEnded:withEvent:. At this point you may discard the path until touchesBegan:withEvent: is called again with the next stroke.

Update

In your code snippets you end up rendering the whole view three times every run loop. That's a lot of drawing overhead. I quickly put together some code that demonstrates what I am talking about above. It is not tested code, but it should be mostly right:

Update 2

I had a bit of time to write and test some code that will do what you are seeking based on my latest two comments. This code is from the view controller instead of the view itself, but with a little modification, you can move in the view if you like. As such, I added an UIImageView as a subview to the generic UIView to hold and display the image (and maintained a reference to in the view controller), and made the view controller the first responder in order to receive the touch events. Lots of hard coded values that you can clean up when integrating into your own project:

static
UIImage *CreateImage( CGRect rect, NSArray *paths )
{
    UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1.0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGFloat colorComponents[4] = {0.0,0.0,0.0,0.0};
    CGContextSetFillColor(context, colorComponents);
    CGContextFillRect(context, rect);

    for ( id obj in paths ) {
        CGPathRef path = (CGPathRef)obj;
        CGContextAddPath(context, path);
    }    
    CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
    CGContextSetLineCap(context, kCGLineCapRound);
    CGContextSetLineWidth(context, 5.0);
    CGContextSetShadow(context, (CGSize){-8.0,40}, 5.0);
    CGContextBeginTransparencyLayer(context, NULL);

    CGContextDrawPath(context, kCGPathStroke);

    CGContextEndTransparencyLayer(context);

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

static
CGPoint MidPoint( CGPoint pt1, CGPoint pt2 )
{
    return (CGPoint){(pt1.x+pt2.x)/2.0,(pt1.y+pt2.y)/2.0};
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    CGPoint location = [[touches anyObject] locationInView:self.view];
    myPath = CGPathCreateMutable();
    CGPathMoveToPoint(myPath, NULL, location.x, location.y);

    [self.paths addObject:(id)myPath];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    UITouch *touch = [touches anyObject];
    CGPoint previousLocation = [touch previousLocationInView:self.view];
    CGPoint location = [touch locationInView:self.view];
    CGPoint midPoint = MidPoint(previousLocation, location);
    CGPathAddQuadCurveToPoint(myPath, NULL, midPoint.x, midPoint.y, location.x, location.y);

    self.imageView.image = CreateImage(self.view.bounds, self.paths);
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    UITouch *touch = [touches anyObject];
    CGPoint previousLocation = [touch previousLocationInView:self.view];
    CGPoint location = [touch locationInView:self.view];
    CGPoint midPoint = MidPoint(previousLocation, location);
    CGPathAddQuadCurveToPoint(myPath, NULL, midPoint.x, midPoint.y, location.x, location.y);

    self.imageView.image = CreateImage(self.view.bounds, self.paths);

    CGPathRelease(myPath);
    myPath = nil;
}

How to know when an user rates an iOS app/game

9 votes

I am developing a game for iOS. I would like to implement a feature that allow the user rate my app and, if he does it, he will get points for my game. I know how to display an screen, menu, whatever to ask the user rate my app, but I don't know how to know when the user does it, I mean, the user completes all the process and I get my valuation.

Thanks so much

The only thing you can do is to use something like Appirater

You could recommend to your clients to review and rate your app. But I think that you don't be able of know if they finally rate or not.

Consecutive calls to startRecordingToOutputFileURL:

8 votes

The Apple docs seem to indicate that while recording video to a file, the app can change the URL on the fly with no problem. But I'm seeing a problem. When I try this, the recording delegate gets called with an error...

The operation couldn’t be completed. (OSStatus error -12780.) Info dictionary is: { AVErrorRecordingSuccessfullyFinishedKey = 0; }

(funky single quote in "couldn't" comes from logging [error localizedDescription])

Here's the code, which is basically tweaks to WWDC10 AVCam sample:

1) Start recording. Start timer to change the output URL every few seconds

- (void) startRecording
{
    // start the chunk timer
    self.chunkTimer = [NSTimer scheduledTimerWithTimeInterval:5
                                                       target:self
                                                     selector:@selector(chunkTimerFired:)
                                                     userInfo:nil
                                                      repeats:YES];

    AVCaptureConnection *videoConnection = [AVCamCaptureManager connectionWithMediaType:AVMediaTypeVideo fromConnections:[[self movieFileOutput] connections]];
    if ([videoConnection isVideoOrientationSupported]) {
        [videoConnection setVideoOrientation:[self orientation]];
    }

    if ([[UIDevice currentDevice] isMultitaskingSupported]) {
        [self setBackgroundRecordingID:[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{}]];
    }

    NSURL *fileUrl = [[ChunkManager sharedInstance] nextURL];
    NSLog(@"now recording to %@", [fileUrl absoluteString]);
    [[self movieFileOutput] startRecordingToOutputFileURL:fileUrl recordingDelegate:self];
}

2) When the timer fires, change the output file name without stopping recording

- (void)chunkTimerFired:(NSTimer *)aTimer {

    if ([[UIDevice currentDevice] isMultitaskingSupported]) {
        [self setBackgroundRecordingID:[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{}]];
    }

    NSURL *nextUrl = [self nextURL];
    NSLog(@"changing capture output to %@", [[nextUrl absoluteString] lastPathComponent]);

    [[self movieFileOutput] startRecordingToOutputFileURL:nextUrl recordingDelegate:self];
}

Note: [self nextURL] generates file urls like file-0.mov, file-5.mov, file-10.mov and so on.

3) This gets called each time the file changes, and every other invocation is an error...

- (void)              captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
                    fromConnections:(NSArray *)connections
                              error:(NSError *)error
{
    id delegate = [self delegate];
    if (error && [delegate respondsToSelector:@selector(someOtherError:)]) {
        NSLog(@"got an error, tell delegate");
        [delegate someOtherError:error];
    }

    if ([self backgroundRecordingID]) {
        if ([[UIDevice currentDevice] isMultitaskingSupported]) {
            [[UIApplication sharedApplication] endBackgroundTask:[self backgroundRecordingID]];
        }
        [self setBackgroundRecordingID:0];
    }

    if ([delegate respondsToSelector:@selector(recordingFinished)]) {
        [delegate recordingFinished];
    }
}

When this runs, file-0 gets written, then we see error -12780 right after changing the url to file-5, file-10 gets written, then an error, then okay, and so on.

It's appears that changing the URL on the fly doesn't work, but it stops the writing which allows the next URL change to work.

Any help would be much appreciated.

Thanks all, for the review and good thoughts on this. Here's the word from Apple DTS...

I spoke with our AV Foundation engineers, and it is definitely a bug in that this method is not doing what the documentation says it should ("You do not need to call stopRecording before calling this method while another recording is in progress."). Please file a bug report using the Apple Bug Reporter (http://developer.apple.com/bugreporter/) so the team can investigate. Make sure and include your minimal project in the report.

I've filed this with Apple as bug 11632087.

Removing done button in media picker

8 votes

In my app I am using media picker when I click on a button media picker will appear. After selecting the required song I have to press done button to dismiss that picker view.
Instead of that can I have a any chance to dismiss picker view after selecting song without pressing done button?

Thanks in advance!

enter image description here

Alternative , You can use allowsPickingMultipleItems property. Set it to NO.

Design pattern for UI controls' state management in iOS

7 votes

Similar to this question, but I am looking for a generic solution or design pattern or framework.

Q. How to add state management into all UI controls in my iOS app automatically without the need to rewrite the existing controls' class?

Example:

e.g. When I click on a UIButton, it will create a new UIWebView showing Google home page. That is easy, but problem arise when user sometimes.. click the button just too fast, so two webview will be displayed.

To solve this question, I would need to make a singleton class which contain the webview, and have a state variable isOpended and if it is true, reuse the existing webview instead of creating a new one.

But the problem is: If I want this behavior in other controls also, then I would need to create many many singleton classes..I am just thinking if there is better way to handle this without the new to re-invent the wheel.

Thanks.

I think you're solving the wrong problem here. Why don't you disable the button until the UIWebView is done processing. That way the user cannot click it twice.

- (IBAction)showMapHomepage:(UIButton*)sender
{
    sender.enabled = NO;
    [self taskThatTakesALongTimeWithCompletion:^{
        sender.enabled = YES;
        // Finish processing
    }];
}

XCode 4.3 Unable to load persistent store UserDictionary.sqlite

7 votes

I have been working on an iOS app and all of a sudden I get the following every time I start the app in the iOS 5.1 Simulator. I am not using Core Data and I am not sure what brought this about. The app still runs but I want to get rid of this. I have removed deleted the app from the simulator, done Clean, and a rebuild but nothing seems to help.

Unable to load persistent store at URL 'file://localhost/Users/jcottrell/Library/Application%20Support/iPhone%20Simulator/5.1/Library/Keyboard/UserDictionary.sqlite' ({ metadata = { NSPersistenceFrameworkVersion = 407; NSStoreModelVersionHashes = { UserDictionaryEntry = ; }; NSStoreModelVersionHashesVersion = 3; NSStoreModelVersionIdentifiers = ( "" ); NSStoreType = SQLite; NSStoreUUID = "43DABF34-7F7E-4FE9-B78D-8AF64292A967"; "_NSAutoVacuumLevel" = 2; }; reason = "The model used to open the store is incompatible with the one used to create the store"; })

I have fixed the problem. I clicked 'iOS Simulator' -> Reset Content and Settings... and it's fixed. Thanks Alex V for your idea it made me poke in the right places.

How does the iOS app Display Recorder record the screen without using private API?

7 votes

The iOS app Display Recorder claims to be able to record the screen of an iOS device, even while it is in the background. Given that UIGetScreenImage() is private API and will lead to a rejection on application submission when detected by the static analysis Apple runs, how were they able to do this recording in an approved application?

Additionally, the app causes a red bar to appear at the top of the screen while it records, similar to the native iOS's phone call functionality.

I've been an iOS developer for awhile, and I'm a bit stumped at how this was even done, even down to the detail of putting the red bar at the top when outside of the app. I was under the impression we basically had no control of what's happening when the app runs in the background, short of some key pieces of functionality (like audio playing, etc).

Even if the developer tapped into private API/libraries to accomplish this, how were they able to do this in a way that wasn't detected during review? My apologies if I'm missing something obvious that was introduced with a later version of iOS here.

Looked into it and it doesnt link against IOSurface. I did however find that it uses dlsym, and after some more reverse engineering, I found this:

/System/Library/Frameworks/IOKit.framework/IOKit
IOServiceGetMatchingServices
IOServiceGetMatchingService
IOServiceMatching
IOMasterPort
IOIteratorNext
IORegistryEntryCreateCFProperty
IOObjectRelease
/System/Library/Frameworks/UIKit.framework/UIKit
UIGetScreenImage
/System/Library/PrivateFrameworks/IOMobileFramebuffer.framework/IOMobileFramebuffer
IOMobileFramebufferOpen
IOMobileFramebufferGetLayerDefaultSurface
/System/Library/PrivateFrameworks/IOSurface.framework/IOSurface
IOSurfaceAcceleratorCreate
IOSurfaceAcceleratorTransferSurface
IOSurfaceLock
IOSurfaceUnlock
IOSurfaceGetWidth
IOSurfaceGetHeight
IOSurfaceCreate
IOSurfaceGetBaseAddress

So, as you see here, after each framework path are the strings of the symbols that it loads from each framework, dynamically. This is to avoid getting in trouble for linking against a Private Framework. Since it is loaded in at runtime, a static analyzer cannot tell that this app uses it, thereby escaping detection.

It does look like my initial suspicion was correct; it is using IOSurface to sneak past sandbox restrictions to have raw screen access. It also uses UIGetScreenImage, which I assume is for the second method of generating video. It also uses some IOKit functions and IOMobileFramebuffer functions. It looks like the app is grabbing an IOSurface from the IOMobileFramebufferGetLayerDefaultSurface function. Not quite sure what it uses IOKit for though.

In conclusion, this app uses some sneaky techniques to avoid detection by static analyzers: it doesn't link against the private frameworks but instead grabs the symbols dynamically. It uses a combination of IOSurface and IOMobileFramebuffer to record the video, or UIGetScreenImage for the other mode. It is a tricky app that WILL get pulled from the AppStore, so if you want it, you better get it now.

iPhone personalized table Rounded corner table

7 votes

I don't have too much experience on iphone screen designs but I need to make a table like this: (image), I made an investigation but I didn't find anything. This table needs to have a rounded corner and the user will be able to insert data, in this case first name, last name, etc... Right now I'm using SDK 4.3. if someone have an tutorial I would appreciate

Thank you,

editable rounded corner table

You can customize table border. All you need to do is import QuartzCore/QuartzCore.h file.

and set following properties in viewDidLoad

tableView.layer.cornerRadius = 10.0f;
tableView.layer.borderColor = [UIColor grayColor].CGColor;
tableView.layer.borderWidth = 1;

UIScrollView not updating and displaying properly

6 votes

Im having problem in my UIscrollView,this is what I have done: Whenever a user picks an image (Multiple or Single) in camera roll thru a Imagepicker, I want to display it in my UIScrollView. I was able to display it, but when I go to the Imagepicker again then pick again an image,it doesnt update the UIScrollView, I tried putting my [self createScrollView] in my viewDidAppear but It recreates the UIScrollView but not update it, so the old images and new images are combined together. So I have put them in viewDidLoadbut It only update when I go to another View Controller then back again.

// My UIScrollView with thumbnail image

- (void) createScrollView {
    self.slotBg = [[UIView alloc] initWithFrame:CGRectMake(43, 370, 300, 143)];
    CAGradientLayer *gradient = [CAGradientLayer layer];
    gradient.frame = self.slotBg.bounds;
    gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor grayColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
    [self.slotBg.layer insertSublayer:gradient atIndex:0];
    [self.view addSubview:self.slotBg];

    self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0f,0.0f,300.0f,134.0f)];
    [slotBg addSubview:self.scrollView];


    int row = 0;
    int column = 0;
    for(int i = 0; i < _thumbs.count; ++i) {

        UIImage *thumb = [_thumbs objectAtIndex:i];
        UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(column*60+10, row*60+10, 60, 60);
        [button setImage:thumb forState:UIControlStateNormal];
        [button addTarget:self 
                   action:@selector(buttonClicked:) 
         forControlEvents:UIControlEventTouchUpInside];
        button.tag = i; 

        [scrollView addSubview:button];

        if (column == 4) {
            column = 0;
            row++;
        } else {
            column++;
        }

    }

    [scrollView setContentSize:CGSizeMake(330, (row+1) * 60 + 10)];
}

// in my viewDidLoad

- (void)viewDidLoad
{
       for(int i = 0; i <= 100; i++) 
        { 
            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
            NSString *documentsDir = [paths objectAtIndex:0];

            NSString *savedImagePath = [documentsDir stringByAppendingPathComponent:[NSString stringWithFormat:@"oneSlotImages%d.png", i]]; 
            NSLog(@"savedImagePath=%@",savedImagePath);
            if([[NSFileManager defaultManager] fileExistsAtPath:savedImagePath]){ 
                [self addImage:[UIImage imageWithContentsOfFile:savedImagePath]]; 

                NSLog(@"file exists");
            } 
        } 
        NSLog(@"Count : %d", [_images count]);
        [self createScrollView];
}

EDIT:

- (void) viewDidLoad {
    [self createScrollView];

    [_thumbs removeAllObjects];
    UIView *removeView;
    for(int i = 0; i < _thumbs.count; ++i) {
    while((removeView = [scrollView viewWithTag:i]) != nil) {
        [removeView removeFromSuperview];
    }

        { 
            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
            NSString *documentsDir = [paths objectAtIndex:0];

            NSString *savedImagePath = [documentsDir stringByAppendingPathComponent:[NSString stringWithFormat:@"oneSlotImages%d.png", i]]; 
            NSLog(@"savedImagePath=%@",savedImagePath);
            if([[NSFileManager defaultManager] fileExistsAtPath:savedImagePath]){ 
                [self addImage:[UIImage imageWithContentsOfFile:savedImagePath]]; 
                NSLog(@"file exists");
            } 
        } 
        NSLog(@"Count : %d", [_images count]);
    }
}

There are two points:

  1. Check for your data source. If image has been saved correctly in your documents directory or not.
  2. No need to create scroll view and its parent view again. Move below code from createScrollView to viewDidLoad. these view should be created only once.

self.slotBg = [[UIView alloc] initWithFrame:CGRectMake(43, 370, 300, 143)]; CAGradientLayer *gradient = [CAGradientLayer layer]; gradient.frame = self.slotBg.bounds; gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor grayColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil]; [self.slotBg.layer insertSublayer:gradient atIndex:0]; [self.view addSubview:self.slotBg];

self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0f,0.0f,300.0f,134.0f)];
[slotBg addSubview:self.scrollView];

Now, from viewDidAppear, first update your _thumbs array (data source). Then call createScrollView function. Inside this function, first remove all the subviews using tag from UIScrollView. Add all the thumbs again as you have done. Then call [self setNeedsDisplay] before returning from createScrollView.