One, preface, why do you want to do free login

  • On January 9, 2017, the long-simmered small program was officially launched. Indeed, The concept of Zhang Xiaolong played incisively and vividly, without downloading, scanning code can be used up and go
  • In 2017, the demographic dividend of the Internet came to an end, so the next step is to improve the conversion rate of traffic in addition to intensive cultivation of content. However, on the way of converting traffic into real users, a login and registration entrance blocks the amount of real money thrown by operation activities.
  • Before talking about free login, I would like to talk about the client login, presumably everyone is familiar with, generally need to include the following aspects [parentheses are divided into optional] :
    • SNS third party quick login
    • Email + (verification code) + password for login and registration
    • Mobile phone number + verification code + (password) login and registration
    • (Prompt the user to log in on the machine last time && account)




  • There is no doubt that the third-party login is the most convenient compared with the login and registration of the mobile phone number and email. On the premise that the third-party app has been logged in, the following two steps are required:
    1. The first time a user opens the app, he or she has to tap on a third-party icon
    2. After jumping to the corresponding APP, click the confirm authorization button to return to your app to complete login




but!!!!!!!!!

  • Before the user has not experienced any highlights of your app, why let the user carry out such complicated operations, do not let the user think! Don’t let the user trouble! Especially today, users increasingly attach importance to privacy!! Not to mention some mai east user account password leakage, said a few days ago some Deli with the hands of big data forced a show superior…




I asked you if you owned a Cadillac would you still use an Autonavi? ! (Silently takes out the metro card in his trouser pocket and looks at it.)

  • The conclusion is that users pay more and more attention to their privacy, and they do not want to have any extra thinking when using the app
  • Therefore, after the user download app for the first time to open, to malicious remove all unnecessary box (in addition to the country iOS10 must pop up cellular network permissions, other receive notification, positioning and other permissions in the best need to pop up again)
  • In addition to the special software (such as voip) must use telephone number registered, other similar electric business, content browsing, dating software, tools and other app, should be to avoid landing operation let user experience, the basic function of the app, add some depth to use on the advanced features on a threshold, prompt the user to log in to register

Two, a few common app examples

1. Today’s headlines:
  • After opening the APP, you can enter the app as a tourist, and you can browse the regular news, view comments, collect, share, feedback and other operations
  • A login box is displayed when you perform operations such as Posting news, commenting, and viewing and reading history








Click on more login effects

  • After the login is successful, the saved data is migrated to an official user name
  • If the login operation is triggered when the user sends a comment, the comment is sent after the login succeeds, indicating that the comment is successfully sent
2. Open your eyes daily
  • Similarly, after entering the APP, you can browse normally, and click “like” in the video state to trigger the login. You can see the female benefactor suspended in the pool, enjoying the soft sunshine and gentle breeze. That graceful figure really makes me, as a user, unable to resist logging in, logging out, logging in again…




But!!





If you think I chose this example because of the photo of the heroine, ha ha, I am not such a superficial person, the eye-catching content and design as well as the overall smoothness of the app are all great, but there are two minor flaws here, in the tourist + landscape mode

  • When watching a video, click the “favorites” button to directly modal the vertical screen login box, which is not very friendly to users
  • After a successful login, operations performed before the login are not automatically continued (Favorites)

    The technical implementation of these two points will be discussed later

Iii. Overall process

  1. After the user enters the APP for the first time, it determines whether the user has logged in on the local computer before. If it is the user’s first login, it will be calledVisitor login APIOf course, this tourist guestId is generated by the server according to the device number. In general, one device corresponds to one tourist guestId, and this tourist guestId cannot be displayed to the user (a last login information can also be returned on this interface to remind the user of the last login method).

    IPhone device all kinds of information access portal

  2. Then use this tourist guestId to initialize various parameters, such as database access address, download file path, browsing records and other aspects of the operation statistics, of course, the tourist in general operations, is to use this tourist guestId to interact with the server
  3. Then we need to consider the specific timing of the pop-up login box. Of course, the product characteristics of each app are different, and the login box will generally pop up in the following situations: deep operations such as favorites, comments, purchasing members, ordering and purchasing goods.
  4. There is no popup login box in the following cases: share, user feedback, add to shopping cart, etc., because these operations are users to take the initiative to help share app, put forward suggestions, this time popup login box, it is a mess!
  5. The pop-up login box (note that somehow the screen adaptation), the user to select after log in, access to a formal user’s userId, to initialize parameters, hide the login page, database migration mergers, download content migration path (mostly download requires the user to the corresponding rights, prevent cheating), historical records migration merge, shopping cart content, migration, etc
  6. Finally, continue to do what the user needs to do before logging in (via block)
  7. If a user logs out, the API for logging out is invoked first, and then the API for logging in to tourists is invoked

Before we go to the code, let’s talk about the details of login and registration

  • After entering the login registration page, the keyboard should pop up immediately, including the pop-up alphabetic keyboard for email and the pop-up numeric keyboard for mobile phone number
  • The Action button should be set to Disabled until both input fields are up to standard
  • Consider small screen adaptation when entering content, and automatically slide to the appropriate position
  • After there is content in the text input box, the ❎ button should be set on the right for users to delete it with one click
  • There is no limit on the length of the account. It is more convenient to judge the phone format in the front end. For example, when becomeFirstResponder becomes the password box, it will directly judge the account format and prompt the user if there is any error
  • The password input box needs to be set with a plaintext button for verification
  • Click the login button to pop up a Daisy (I mean UIActivityIndicatorView, not the soap Daisy) or an animation to prevent multiple network requests
  • For login registration information error, this is best to be able to do timely feedback, consider the web side of the account registration, whether the nickname has been occupied can be prompted in the user input, if every time excitedly input a lot of messages, full of expectations click the registration button, the result prompt “your nickname has been occupied”, Will you like the site any less? So it’s good to be able toPrompt the user in a way that ensures smooth user behavior, such as
    • The nickname is limited to 10 bits, so it should be invalid when entering the 11th bit
    • The best unified login registration interface: users input mobile phone number, email, real-time query whether the database has been registered, and then update the button status
  • You also need to consider abnormal information such as network timeout, request error, server breakdown, and SMS failure
  • For some financial related apps, in order to prevent the server from being attacked (of course), do you want to consider adding the verification code after the same IP request twice (countdown is usually fixed code in front)?
  • If the login fails, the prompt information must be accurate, such as verification code error, or account name and password error although this prompt information is generally done by the server students

5. Code Design: Don’t say anything, it’s all in the code

1. First write a popup view method in the global controller management class
/** In most cases, the default way to add directly to the top controller * title: pop-up login box prompt, such as after logging in to comment * block: the user is blocked by the login box operation (note the circular reference) */
- (void)transferControlToPortalViewWithTitle:(NSString *)title block:(void((^)))block;Copy the code
2. Then judge in the click events that prompt visitors to log in for deep operations such as collection
- (void)favoredBtnTapped:(UIButton *)sender {
    // If it is a tourist account, the user will be prompted to log in, otherwise the normal favorites button click event will be carried out
    if ([self.systemAccountManager isGuest]) {
       [self.systemVCManager transferControlToPortalViewWithTitle:@" After login can be favorites operation" block:^{
           [weakSelf doFavoredAction];
       }];
    } else{[selfdoFavoredAction]; }}Copy the code
3. For example, to implement the above mentionedToday’s headlineThe login box in the style of present and modal cannot be used, because then the controller view of the upper level will be moved to another Window, and the effect of adding a translucent mask to the original interface cannot be realized. Therefore, the following method is used
[fatherVC addChildViewController:portalVC];
[fatherVC.view addSubview:portalVC.view];Copy the code

Here is a correction, thanks to the method proposed by CZAnchor, which can be implemented by present method, the code is as follows:

UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController;
UIViewController *baseVC = rootVC;

while (baseVC.presentedViewController) {
   baseVC = baseVC.presentedViewController;
}

if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
    portalVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;
    baseVC.definesPresentationContext = YES;
    [baseVC presentViewController:portalVC animated:NO completion:^{}];
} else {
    baseVC.modalPresentationStyle = UIModalPresentationCurrentContext;
    [baseVC presentViewController:portalVC animated:NO completion:^{}];
}Copy the code
4. In the successful callback of invoking the login interface, two operations are required
4.1 Data Migration first:
  • For the migration of downloaded content files, each account corresponds to a storage path because some downloaded content requires corresponding permissions, which also prevents the loss of interests caused by excessive sharing of accounts to a certain extent
/** Migrate downloaded files */
# Warning There are two parts to consider when downloading content in visitor state:
1.The official user who logs in has not logged in on the local computer before. After creating the download path of the user, the downloaded content of tourists will be directly migrated to the past (if the user has only logged in without downloading content, it will be directly migrated to the past).2.If the user has logged in to the computer before and downloaded content, it is necessary to merge the downloaded content under the two paths - (void)transferDownLoadedFile {
    // Obtain the root path of the downloaded file
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory.NSUserDomainMask.YES);
    NSString *libraryDir = [paths objectAtIndex:0];
    NSString *rootFilePath = [NSString stringWithFormat:@ % @ / % @ "",libraryDir,Here is the path to download the file in the project ##];

    // Get the download path of tourists and official users respectively (use the corresponding ID as the path name for convenience)
    NSString *guestPath = [NSString stringWithFormat:@ % @ / % @ "", rootFilePath, self.accountManager.guestId];
    NSString *userPath = [NSString stringWithFormat:@ % @ / % @ "", rootFilePath, self.accountManager.userId];

    // Get the file manager
    NSFileManager *manager = [NSFileManager defaultManager];

    // Get the visitor's download file array
    NSError *error = nil;
    NSArray *guestFilesArr = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:guestPath error:&error];
    if (error) {
        NSLog(@"contentsOfDirectoryAtPath guestPath:%@", error);
    }

    // Walk through the visitor's files
    for (NSString *fileName in guestFilesArr) {
        // Concatenate the path where the file is stored in the visitor state && official user state
        NSString *guestFileDir = [guestPath stringByAppendingPathComponent:fileName];
        NSString *userFileDir = [userPath stringByAppendingPathComponent:fileName];
        // If the file is not included in the official user download file, create it
        if(! [manager fileExistsAtPath:userFileDir]) { [manager createDirectoryAtPath:userFileDir withIntermediateDirectories:YES attributes:nil error:&error];
        }

        BOOL isDir;
        if ([manager fileExistsAtPath:guestFileDir isDirectory:&isDir] && isDir) {
            error = nil;
            NSArray *childFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:guestFileDir error:&error];
            if (error) {
                NSLog(@"contentsOfDirectoryAtPath dir:%@", error);
            }
            // Go through the subfiles in this folder and migrate them to the files under the official user name
            for (NSString *childFile in childFiles) {
                NSString *filePath = [guestFileDir stringByAppendingPathComponent:childFile];
                NSString *destPath = [userFileDir stringByAppendingPathComponent:childFile];
                error = nil;
                [manager moveItemAtPath:filePath toPath:userFileDir error:&error];
                if (error) {
                    DDLogError(@"moveItemAtPath to path error:%@", error);
                    // If the file exists under the official user (i.e. the user has logged in and downloaded the file before), an error will be reported, then the changed file under the tourist path will be deleted
                    [manager removeItemAtPath:filePath error:&error];
                }
            }
        }
    }
}Copy the code
  • Database migration: this part is really too much related to the business and packaging of the project. Here, we take the download record of a video file as an example and take FMDB as the carrier to give a general idea
Obtain the guest database file path guestDataBasePath
// 2. Open the db file of the visitor
fmDataQueue = [FMDatabaseQueue databaseQueueWithPath:path];
 [fmDataQueue inDatabase:^(FMDatabase *fmDatabase) {
     if ([fmDatabase open]) {
        [fmDatabase setShouldCacheStatements:YES];
        // Create an SQL statement
        NSString *sqlStr = [NSString stringWithFormat:@"% @ % @ % @ % @ % @ % @ % @"The @"CREATE TABLE IF NOT EXISTS MYVIDEO (VIDEOID TEXT PRIMARY KEY "The @",videoname TEXT"The @",info TEXT"The @",coverfilename TEXT"The @",urlpath TEXT""); BOOL isExecute = [fmDatabase executeUpdate:createStatement];if (isExecute) {
             // If necessary, check to see if the table structure has been upgraded
         } else {
             NSLog(@"error occured while creating MYVIDEO table"); }}else {
     NSLog(@"open datebase failed"); }}// 3. Query the downloaded video under the tourist account
// Create an empty array to hold the video object
NSMutableArray *videoArray = [[NSMutableArray alloc] init];
[fmDataQueue inDatabase:^(FMDatabase *fmDatabase) {
    // Write SQL statements
    NSString *query = [NSString stringWithFormat:@"SELECT videoid,videoname,info,coverfilename,urlpath, FROM MYVIDEO "];
    NSString *sqlQuery;
    if(wheresql ! = nil) { sqlQuery = [NSString stringWithFormat:@"% @ % @".query, wheresql];
    } else {
        sqlQuery = query;
    }
    // Sort by time in descending order
    sqlQuery = [sqlQuery stringByAppendingString:@" ORDER BY time DESC "];
    FMResultSet *resultSet = [fmDatabase executeQuery:sqlQuery];
    if ([fmDatabase hadError]) {
        NSLog(@"FMDB Error %d: %@", [fmDatabase lastErrorCode], [fmDatabase lastErrorMessage]);
    }
    // Retrieve the result set of the query
    while ([resultSet next]) {
        VideoClass *video = [[VideoClass alloc] init];
        video.videoId               = [resultSet stringForColumn:@"videoid"];
        video.videoTitle            = [resultSet stringForColumn:@"songname"];
        video.videoDescription      = [resultSet stringForColumn:@"info"];
        video.coverFileName          = [resultSet stringForColumn:@"coverfilename"];
        video.path                   = [resultSet stringForColumn:@"urlpath"];

        [videoArray addObject:video];
    }
    [resultSet close];
}];

// 4. Close visitor db
[fmDataQueue inDatabase:^(FMDatabase* fmDatabase) {
    if ([fmDatabase close]) {
        NSLog(@"close MYVIDEO succes ....");
    }
    else {
        NSLog(@"close MYVIDEO error"); }}]; [fmDataQueueclose];
fmDataQueue = nil;

// 5. Open the db file under the official user (after obtaining the tourist DB path, the code is the same as above to open the tourist DB)

// 6. Insert the video data downloaded by tourists into the db of official users
[fmDataQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    [array enumerateObjectsUsingBlock:^(VideoClass  *video, NSUInteger idx, BOOL * _Nonnull stop) {
        [self insertOrUpdateCourse:video withDB:db];
        // Create an SQL statement to insert data
        NSString *insertSql = @"INSERT OR REPLACE INTO MYVIDEO (videoid,videoname,info,coverfilename,urlpath,) VALUES(? ,? ,? ,? ,?) ";
        BOOL result = [fmDatabase executeUpdate:insertSql,
                    video.videoId,
                    video.videoTitle,
                    video.videoDescription,
                    video.coverFileName,
                    video.urlPath];
        if(! result) { NSLog(@"Pissed! Insert MYVIDEO data failed");
        } else {
            NSLog(@"The cow force! Insert MYVIDEO data success, U did it!"); }}]; }];// 7. Delete the tourist db file based on the tourist DB path
NSFileManager *fm = [NSFileManager defaultManager];
BOOL success = [fm removeItemAtPath:fullPath error: &error];
if (error) {
      NSLog(@"How can deletion fail? Am I in the wrong posture? delete file at path error:%@".error);
}Copy the code
4.2 Then hide the login interface and call the previous block to continue the user’s previous operations
- (void)hidePortalView {
    if (self.loginSucessBlock) {
        self.loginSucessBlock();
    }
    UIView animateWithDuration:0.2 animations:^{
        self.portalVC.view.alpha = 0;
    } completion:^(BOOL finished) {
        [self.portalVC.view removeFromSuperview];
        [self.portalVC removeFromParentViewController]; }}Copy the code
5. Perform horizontal and vertical screen adaptation
  • Since the view with a translucent background mask is implemented in addChildViewController mode, it automatically ADAPTS to the vertical and horizontal screen of the parent controller. Here we mainly talk about the vertical and horizontal screen adaptation of the traditional login registration page where you click another login method to enter the account password again
- (void)signInWithAccountBtnTapped:(UIButton *)sender {
    SignInController *signInVC = [[SignInController alloc] initWithType:InputViewLogin];

    // Set the modal mode of the controller to follow the environment of the current controller, so that the current screen is modal in horizontal screen mode
    signInVC.modalPresentationStyle = UIModalPresentationCurrentContext;

    [self presentViewController:signInVC animated:YES completion:nil];
}Copy the code
  • Of course, there is also some UI-level adaptation inside SignInController, implementing the following methods inside its viewWillAppear method
// Get the horizontal and vertical screen information of the current page according to the direction of the status bar
UIDeviceOrientation deviceOrientation = (UIDeviceOrientation) [UIApplication sharedApplication].statusBarOrientation;
// According to the horizontal and vertical screen state, make the corresponding UI level adjustment, and make the corresponding mark
if (deviceOrientation == UIDeviceOrientationPortrait ||deviceOrientation ==
    UIDeviceOrientationPortraitUpsideDown) {[self doPortraitUIAdjustment];
    self.isLandScape = NO;
} else{[self doLandScapeUIAdjustment];
    self.isLandScape = YES;
}Copy the code
  • However, running the code found that although the horizontal and vertical display was correct, after clicking the input box, the keyboard was still displayed in portrait mode, because we just adapted the modal mode of SignInController and UI, at this time the controller itself did not know whether it was landscape or portrait. Therefore, the following three controller methods will be overridden
// In landscape mode, the overall landscape should be automatically flipped in both directions according to the device gravity sensing
- (BOOL)shouldAutorotate {
    if (self.isLandScape) {
        return YES;
    } else {
        return NO; }}// In landscape mode, both directions of the landscape camera should be supported, while only Portrait is supported
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    if (self.isLandScape) {
        return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight;
    } else {
        return UIInterfaceOrientationMaskPortrait; }}// The default direction- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    if (self.isLandScape) {
        return UIInterfaceOrientationLandscapeRight;;
    } else {
        return UIInterfaceOrientationPortrait; }}# Warning At this point, horizontal and vertical adaptation is completeCopy the code

The general idea is these, because the project is relatively relevant, and the code implementation is relatively simple, so there is no demo, if you have other questions, welcome to communicate in the message area