开发者

Xcode: How would I do a one-off UIAlertView?

I have an app that I want to bring up an开发者_运维问答 alert view to ask them if they can review the app?

Any help appreciated!


Don't. That is bad UX. Instead, try the following:

  • have an "About" section in your app containing your brand, and a link to the review page
  • have a "Review This App" button in a non-distracting location at a corner/side of the screen where it doesn't break the user experience


Something like this would do it:

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"UIAlertView" message:@"<Alert message>" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
[alert show];
[alert release];


* Important Update

Apple has added an extra rule for apps being submitted to the app store.

We can no longer store arbitrary data files in the Documents folder. Only content generated by the user like a text file they manually typed and saved or a photo they took with the camera can be saved in the Documents folder.

We are now expected to store our app generated files in the Library/Cache folder instead of the Documents folder. In addition, we should mark the files we don't want to be sync to iCloud with the skip backup attribute.

Failure to comply with this will result in an app being rejected by Apple.

Reason: The Documents folder is now used for syncing to iCloud. iCloud does a sync every few minutes and if we were to have megabytes of our app generated files stored in the Documents folder, it would get mixed up with the user's own iCloud synced files.

Update the MediaDirectory.m files with these new code to be safe:

+ (NSString *) mediaPathForFileName:(NSString *) fileName
{   
    NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cachesDirectory = [directoryPaths objectAtIndex:0];
    NSString *filePath = [NSString stringWithFormat:@"%@/%@", cachesDirectory, fileName];

    return filePath;
}

+ (BOOL)addSkipBackupAttributeToFile:(NSString *) fileName
{
    NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cachesDirectory = [directoryPaths objectAtIndex:0];
    NSString *filePathStr = [NSString stringWithFormat:@"%@/%@", cachesDirectory, fileName];

    const char* filePath = [filePathStr fileSystemRepresentation];

    const char* attrName = "com.apple.MobileBackup";
    u_int8_t attrValue = 1;

    int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
    return result == 0;
}

Remember to mark that text file we generated with the skip backup attribute like so after generating the file:

[MediaDirectory addSkipBackupAttributeToFile:@"fileStatus.txt"];

I also had a typo earlier, I said to write to file "firstLaunch.txt" when it should have been "fileStatus.txt" (bad copy and paste skills :P)

Original Text

You could try writing a value (such as "no") to a text file and storing in the application's sandbox Document folder when the user taps on "Don't ask again" button.

Next time the app loads, your view controller would read the value from this text file and if it's "no", then don't display the alert view.

Hope that helps.

Writing a one off UI alert view

OK, let me first explain the process we're going to go through:

1) Your application delegate class didFinishLaunchingWithOption is the method that gets called when an app is launched.

2) This method only gets called once when the app launches so this is the spot where we create a text file and we write our status of the alert view whether to "show again" or "don't show again" for the UIAlertView

3) We're going to bring up the alert view in your UIViewController's viewDidLoad method. However, we only display this UIAlertView IF the value in our text file IS NOT "don't show again". If the value in the text file is "show again", we show the UIAlertView, if it's "don't show again", we don't show the UIAlertView, makes sense?

(These values are only string, you can set any value you want, I'm only using these random values to demonstrate).

Right, now we got the general process, let's implement it.

The code

In your AppDelegate.h header file:

@interface MyAppDelegate : NSObject
{
    ...
    bool isFirstLaunch;
}

Now in your AppDelegate.m implementation file:

// -------------------------------------------------------------------------------------
// I wrote a helper class called "MediaDirectory" that returns me the path to a file
// in the Documents directory of the application sandbox.
// Each application has a copy of the Documents directory so you can safely
// assume this directory will always exist.
//
// This class has been very useful for me, hope you find it useful too.
//
// DOWNLOAD THE FILES AT: 
// 
// http://www.chewedon.com/classes/MediaDirectory.h
// http://www.chewedon.com/classes/MediaDirectory.m
// -------------------------------------------------------------------------------------

#import <Foundation/NSFileManager.h>
#import <Foundation/NSFileHandle.h>
#import "MediaDirectory.h"

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ...

    NSString *fileStatus = nil;

    // Here, the NSFileManager defaultManager fileExistsAtPath method needs a path to 
    // the file in the Document directory, to get this path, use the static method from
    // my MediaDirectory class and pass it the name of the file.
    // I called it "fileStatus.txt" 
    //
    // We're checking if the file exist in the Documents directory or not. 
    // If it does not exist then we create the text file and pass it the
    // value "show again", otherwise we do nothing.
    // (We don't want to overwrite the value everytime the app starts)
    if([[NSFileManager defaultManager] fileExistsAtPath:[MediaDirectory mediaPathForFileName:@"fileStatus.txt"]] == NO)
    {            
        // prepare fileStatus.txt by setting "show again" into our string
        // we will write this string into the text file later
        fileStatus = [[NSString alloc] initWithString:@"show again"];

        // now we write the value "show again" so that the UIAlertView will show
        // when it checks for the value, until the user clicks on no and we set
        // this value to "don't show again" later in another piece of code
        [fileStatus writeToFile:[MediaDirectory mediaPathForFileName:@"fileStatus.txt"] 
                      atomically:YES 
                        encoding:NSUTF8StringEncoding 
                           error:nil];
    }

    [fileStatus release];

    ...

    // rest of your didFinishLaunchingWithOptions method
    [window addSubview:[viewController view]];
    [self.window makeKeyAndVisible];
    [self initGlobals];

    return YES;
}

Now in your UIViewController class, we need to make this class conform to the UIAlertView protocol. We are going to use one of the delegate method that tells us when a button on the UIAlertView is clicked.

@interface MyViewController : UIViewController <UIAlertViewDelegate>
{
   ...
}

In our implementation file (MyViewController.m file) we check the value stored in the text file before showing the UIAlertView.

#import "MediaDirectory.h"
#import <Foundation/NSFileManager.h>
#import <Foundation/NSFileHandle.h>

-(void)viewDidLoad
{   
    ...

    BOOL shouldShowAlert = false; 
    NSString *fileStatus = nil;

    // check if the file exists in the Documents directory and read its value
    // if it does. If the value read from file is "show again",
    // we bring up a UIAlertView
    if([[NSFileManager defaultManager] fileExistsAtPath:[MediaDirectory mediaPathForFileName:@"fileStatus.txt"]] == YES)
    {
        fileStatus = [[NSMutableString alloc] initWithContentsOfFile:[MediaDirectory mediaPathForFileName:@"fileStatus.txt"]
                                                             encoding:NSUTF8StringEncoding
                                                                error:nil];

        if([fileStatus isEqualToString:@"show again"] == YES)
        {
            shouldShowAlert = true;
        }
        else if([fileStatus isEqualToString:@"don't show again"] == YES)
        {
            shouldShowAlert = false;
        }
    }

    if(shouldShowAlert == true)
    {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"My Alert View Title" 
                                                    message:@"my message" 
                                                   delegate:self 
                                          cancelButtonTitle:@"OK" 
                                          otherButtonTitles:@"Don't Show Again", nil];

        // we set a tag for this UIAlertView to distinguish between this UIAlertView
        // and other UIAlertView in case there are any.
        // I use a value of 10, you can use any value but make sure it is unique
        [alert setTag:10];

        [alert show];
        [alert release];
    }
}

Now we come to the last part where we handle which button the user tapped on for the UIAlertView.

Somewhere in your MyViewController.m file, write this delegate method:

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    // making sure its the UIAlertView we want
    if([alertView tag] == 10)
    {
        // By default, the "cancel" button has an index of 0
        // and the next button would have a index of 1
        // In our case, we set the first button is "OK"
        // and "Don't Show Again" as second button 
        if(buttonIndex == 1)
        {
            NSString *fileStatus = [[NSString alloc] initWithString:@"don't show again"];

            [fileStatus writeToFile:[MediaDirectory mediaPathForFileName:@"fileStatus.txt"] 
                  atomically:YES 
                    encoding:NSUTF8StringEncoding 
                       error:nil];   

            [fileStatus release];
        }
    }
}

Hopefully I didn't miss anything, let me know if it works or not.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜