Day 5 Task

Today, I mainly finish the processing of cell contents in the essence page.

  1. Cell height calculation
  2. Displays the contents in the middle of the cell
  3. The reconstruction of the essence module
  4. Review images
  5. Save pictures to albums

Cell height calculation

Cell spacing Settings, there are 10 to the spacing between each cell, because the cell reuse mechanisms, we found that even in the tableView: didDeselectRowAtIndexPath method by clicking on the cell, reduce the height of the cell, When the cell is displayed again, it will still change back to its original height, and the system has processed the cell internally. The frame of the cell has been set internally, so we intercept the frame of the system set cell by rewriting the setFrame method of the cell. We will do some processing first. Then let the system set.

- (void)setFrame:(CGRect)frame {// Set the height of the cell by 10, then set it internally. frame.size.height -= XMGMargin; [super setFrame:frame]; }Copy the code

Computing the height of a cell The height of a cell needs to be calculated based on the contents of each cell. Add the Type attribute to the model to distinguish the contents in the middle of the cell. Enumerations are used here.

Typedef NS_ENUM(NSInteger, CLTopicType) {/** all */ CLTopicTypeAll = 1, /** picture */ CLTopicTypePicture = 10, /** CLTopicTypeWord = 29, /** CLTopicTypeVoice = 31, /** video */ CLTopicTypeVideo = 41,};Copy the code
/** Intermediate content type */ @property(nonatomic,assign)CLTopicType type;Copy the code

In addition, we can divide the cell height calculation into five parts, as shown in the figure below




Cell highly computational analysis

Cell content, text and image height can only be obtained from the model, so add cellHeight attribute and contentF attribute to the model, rewrite cellHeight get method to calculate the height of the cell. The fRAM calculated for the intermediate content is stored in contentF, which is then used to set the frame of the intermediate content in the cell.

Calculate the height of the code, where the attention has been commented.

CellHeight {// iOS8 start cell does not cache cell height, each time the display cell will come here to calculate the cell height, // If you have calculated the height once, do not calculate it again, just return the height of the model. // if (_cellHeight)return _cellHeight; If (_cellHeight == 0) {// 1. CGFloat textMaxW = [UIScreen mainScreen].bounds.size.width - 2 * CLMargin; CGSize textMaxSize = CGSizeMake(textMaxW, MAXFLOAT); // CGSize textSize = [self.text sizeWithFont:[UIFont systemFontOfSize:15] constrainedToSize:textMaxSize]; CGSize textSize = [self.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]} context:nil].size; _cellHeight += textSize.height + CLMargin; If (self.type! = CLTopicTypeWord) {// The Height of the image needs to be calculated according to the ratio of the maximum width that can be displayed  / self.width; If (Height >= [UIScreen mainScreen].bounds.size. Height) {Height = 250; self.isBigPicture = YES; } self.contentF = CGRectMake(CLMargin, _cellHeight, textMaxW, Height); _cellHeight += Height + CLMargin; If (self.top_cmt) {if (self.top_cmt) { NSString *contentText = topic.top_cmt.content; If (top.top_cmt.voiceuri.length) {contentText = @"[Voicemail]"; if (top.top_cmt.voiceuri.length) {contentText = @"[voicemail]"; } NSString *topCmtContent = [NSString stringWithFormat:@"%@ : %@",self.top_cmt.user.username,contentText]; // CGSize topCmtContentSize = [topCmtContent sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:textMaxSize]; CGSize topCmtContentSize = [topCmtContent boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin  attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:14]} context:nil].size; _cellHeight += topCmtContentSize.height + CLMargin; } // 5. Bottom bar + cell spacing 10_cellHeight += 35 + CLMargin; } return _cellHeight; }Copy the code

And then finally, in tableView: heightForRowAtIndexPath: get the model and just return cellHeight

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {// return the height of the cell self.topicArr[indexPath.row].cellHeight; }Copy the code

At the height of the cell have decided according to the content of each cell shows different and need to emphasize here a question: the height of the cell is recalculated every time there is no need to show it again, so first to determine cellHeight can be returned directly if there are value, no value in the calculation, avoid unnecessary and time-consuming calculation.

Displays the contents in the middle of the cell

The middle content of the cell is divided into four modules: video, audio, picture, and paragraph. The section does not have a picture display, we use xiB to describe the video, audio, and picture display respectively. As shown in figure




Video xib




Audio xib




Image xib

The picture here is divided into ordinary picture, GIF picture, long picture. Need to determine the GIF logo ImageView according to the different image and click to see whether the big picture Button is hidden.

In CLTopicCell, the setTopic: method determines the type of the intermediate content and determines the content to be displayed

#pragma mark - Intermediate data type if (topic.type == CLTopicTypeVideo) {self.videoView.hidden = NO; self.videoView.frame = topic.contentF; self.videoView.topic = topic; self.voiceView.hidden = YES; self.pictureView.hidden = YES; }else if (topic.type == CLTopicTypeVoice){ self.videoView.hidden = YES; self.voiceView.hidden = NO; self.voiceView.frame = topic.contentF; self.voiceView.topic = topic; self.pictureView.hidden = YES; }else if (topic.type == CLTopicTypeWord){ self.videoView.hidden = YES; self.voiceView.hidden = YES; self.pictureView.hidden = YES; }else if (topic.type == CLTopicTypePicture){ self.videoView.hidden = YES; self.voiceView.hidden = YES; self.pictureView.hidden = NO; self.pictureView.frame = topic.contentF; self.pictureView.topic = topic; }Copy the code

Note: Due to the reuse mechanism of cells, the cell with video content in the middle is likely to be reused in other cells. Therefore, it is necessary to display its own content and hide the views of the other two types of content to prevent confusion. There is no picture displayed in the sub-cell, so it is necessary to hide all the views of the other three types of cells.

In addition, set the frame of the view according to the frame of the intermediate content stored in the model. At this time, it is found that although the calculated frame of the intermediate content is correct, only the x and y values displayed in the cell are correct, while width and height are incorrect.

This is due to the use of automatic layout in XIBS, The control of the load from the xib autoresizingMask default is UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight, automatically control scale according to the parent, So width and height are incorrect. Only need to-(void)awakeFromNibMethod to cancel its scaling effectself.autoresizingMask = UIViewAutoresizingNone;

Display of intermediate content pictures

The image URL of the middle content can be obtained through the model, so add model attributes to the three types of View, and when setting the View display according to the type in the cell, assign the model value to the model attribute of the View, and get the model attribute to get the URL of the middle image. The video and audio servers also provide an image for display and assign values to the iamgeView based on the url of the image returned by the server.

The image setup is a little more complicated. The database returns us three images, small, medium and original. We’ll use the original first. Just set the image of the imageView in the View’s setTopic method.

You need to determine whether the image is a GIF image or a long image.

If (topic.is_gif) {self.gifImageView.hidden = NO; }else{ self.gifImageView.hidden = YES; } if(topic.isbigpicture){self. seeBigbutton. hidden = NO; / / set the imageView out, will be subject to display at the top of the self. The imageView. ContentMode = UIViewContentModeTop; self.imageView.clipsToBounds = YES; }else{ self.seeBigButton.hidden = YES; / / if not need to set a larger version of reduction, prevent cell reuse set self. ImageView. ContentMode = UIViewContentModeScaleToFill; self.imageView.clipsToBounds = NO; }Copy the code

To determine whether it is a large picture, we need to add isBigPicture property, and return to the method of setting cell height, if the height of the middle content is more than one screen height, it means that it is a long picture. Set isBigPicture to YES. In addition, we also need to pay attention to the problem of cell reuse. To set the display of GIF logo and view the large image button display, we need to set hidden in the relative method to prevent confusion during cell reuse.

At this point, the intermediate content can be displayed, but some details need to be done.

  1. Long graph display processing, at this point we see the long graph display is like this




    Long graph display is not processed


    The image is compressed and filled in the ImageView. Change the contentMode of the ImageView to determine if it is a long image

    / / set the content of the imageView to top alignment showed that excess will be cut off the self. The imageView. ContentMode = UIViewContentModeTop;Copy the code

    Also, prevent cell reuse from occurring if not long graph need to be set

    self.imageView.contentMode = UIViewContentModeScaleToFill;Copy the code
  2. Picture shows progress bar, progress bar using DACircularProgress third party. The method is very simple and will not be repeated here. You can use the SD method to monitor the download progress.

    / / set the image and display the progress [self imageView sd_setImageWithURL: [NSURL URLWithString: topic. Large_image] placeholderImage: nil options: 0 Progress :^(NSInteger receivedSize, NSInteger expectedSize) {// receivedSize: The download progress // expectedSize: Full size CGFloat progress = 1.0 * receivedSize/expectedSize; self. ProgressView. Progress = progress;  self.progressView.progressLabel.text = [NSString stringWithFormat:@"%.0f%%",progress * 100];  self.progressView.hidden = NO;  } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { self.progressView.hidden = YES; }];Copy the code
  3. Video and audio view playback times, playback times display is very simple, and video and audio the same, just modify the control can be

    -(void)setTopic:(CLTopic *)topic { _topic = topic; [self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.large_image]]; NSInteger minute = topic.videotime / 60; NSInteger second = topic.videotime % 60; self.videoTimeLabel.text = [NSString stringWithFormat:@"%02zd:%02zd",minute , second]; Self.playcountlabel. text = [NSString stringWithFormat:@"%zd times play ",topic.playcount]; }Copy the code
  4. Monitor network status for simple optimization of display pictures

    As mentioned above, the picture data returned to us by the server includes three types of small graph, medium graph and large graph. We can use AFN to judge the current network of the user. If the current user is using cellular network, the small graph can be loaded to save traffic for the user and speed up the picture display speed in the cell. If the user is in wifi environment, the original image will be loaded to improve the image clarity. If the user does not currently have a network, the user is reminded that there is no network.
    / / used for network diagnosis AFN AFNetworkReachabilityStatus status = [AFNetworkReachabilityManager sharedManager].networkReachabilityStatus; If (status = = AFNetworkReachabilityStatusReachableViaWWAN) {/ / cell phones bring network [self. ImageView sd_setImageWithURL: [NSURL URLWithString:topic.small_image]]; } else if (status == AFNetworkReachabilityStatusReachableViaWiFi) { // WIFI [self.imageView sd_setImageWithURL:[NSURL URLWithString:topic.large_image]]; Self.imageview. image = nil; self.imageview. image = nil; }Copy the code

    Note: the emulator cannot distinguish between network states and requires a real machine test.

The reconstruction of the essence module

After the completion of all interfaces, we found that the page display of video, audio, picture and segment was very simple. We could directly copy the code of all interfaces and modify the parameters of data request. 1 is all, 41 is video, 31 is audio, 10 is picture and 29 is segment. However, this results in a lot of duplicate code. The code in the five sub-controllers of the elite controller is basically the same, so you can use inheritance to reconstruct the code. Create the base class CLTopicViewController inheriting from UITableViewController, and the other five subclasses inheriting from CLTopicViewController, again copying the code. There are many ways to refactor, and we compare and choose the best one.

  1. Determine the request parameters based on the controller type

    if ([NSStringFromClass(self.class) isEqualToString:@"CLAllViewController"]) {
     params[@"type"] = @"1";
    } else if ([NSStringFromClass(self.class) isEqualToString:@"CLVideoViewController"]) {
     params[@"type"] = @"41";
    } else if ([NSStringFromClass(self.class) isEqualToString:@"CLVoiceViewController"]) {
     params[@"type"] = @"31";
    } else if ([NSStringFromClass(self.class) isEqualToString:@"CLPictureViewController"]) {
     params[@"type"] = @"10";
    } else if ([NSStringFromClass(self.class) isEqualToString:@"CLWordViewController"]) {
     params[@"type"] = @"29";
    }Copy the code

    Disadvantages: requires a lot of tedious code, pull down refresh and pull up load need to be re-judged, and here the parent controller sets the type of the child controller, violating the code principle of whose content should be managed by itself.

  2. Add a Type attribute to the base class

    // @property (nonatomic, assign) CLTopicType type;Copy the code

    Then we can set the type property of the child controller when we add the child controller to the master controller, for example, the other child controllers are the same.

    CLAllViewController *all = [[CLAllViewController alloc]init];
    all.type = CLTopicTypeAll;
    [self addChildViewController:all];Copy the code

    In fact, at this time we directly use the base class, add five main controller base class for controllers, each base class controller type attribute is different, but doing so is very limited, if you have any demand after need to add separate sub controller controls, or personalized Settings, still need to judge in the base class, ductility is very bad.

  3. By overwriting the get method of the type attribute of the base class, we can override the get method of the base class in the subclass and return type. The get method can only be overwritten by the subclass. There is no way for other classes to change the type of the subclass. It ensures that a certain content in the parent class can only be modified or provided by the child class, not by the outside world. Moreover, we can make some personalized Settings on the interface of the child class in the child class, which is very extensible.

    - (CLTopicType)type
    {
     return CLTopicTypeAll;
    }Copy the code

    It is also possible to add readonly to the property, which generates a get method of type and a _type member variable, creating more unnecessary member variables than the above method. And you need to consider code ordering, which can be problematic if you have some calls to the Type attribute in the parent class, because type is set after the super method.

So far we have refactored the quintessential module by inheriting and overwriting the GET method of Type. The code inside the child controller becomes very simple, just overriding the get method that overrides the parent class, and you can customize the subclass in the subclass.

Review images

For the image cell, clicking on the image Mode a controller to display the image, again using the XIB to describe the image display controller, creating the CLSeeBigViewController controller, using the XIB to describe the controller view




The view of CLSeeBigViewController

In the imageView, CLTopicPictureView, add a click event to the iamgeView that displays the image in the middle. ImageView does not support interaction by default, so you need to enable interaction.

self.imageView.userInteractionEnabled = YES;
[self.imageView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(seeBig)]];
- (void)seeBig
{
    CLSeeBigViewController *seeBig = [[CLSeeBigViewController alloc] init];
    seeBig.topic = self.topic;
    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:seeBig animated:YES completion:nil];
}Copy the code

The next thing you need to do is make some judgments in CLSeeBigViewController. First of all, it might be a long graph, which is definitely larger than a screen size, so in CLSeeBigViewController you need to use a scrollView to display the long graph, Since the xiB has already added the return and save buttons to the View of the CLSeeBigViewController, the scrollView needs to use insertSubview:atIndex at the bottom layer to prevent a later scrollView from overwriting the return and save buttons. And add an imageView to the scrollView. Calculate the length of the image. If the length does not exceed one screen size, calculate the height of the image based on the width to height ratio of the screen. Center the image in the screen to ensure that the imageView takes up the entire width of the screen. If the length is more than one screen size, set the y value of the imageView to 0, the contentSize of the scrollView to 0 horizontally, and the height of the image vertically. Finally, the scale of the imageView is set through the proxy method of scrollView.

#import "clSeeBigViewController.h" #import @interface CLSeeBigViewController () /** image control */ @property (nonatomic, weak) UIImageView *imageView; @end @implementation CLSeeBigViewController - (void)viewDidLoad { [super viewDidLoad]; // scrollView UIScrollView *scrollView = [[UIScrollView alloc] init]; scrollView.delegate = self; scrollView.frame = [UIScreen mainScreen].bounds; [self.view insertSubview:scrollView atIndex:0]; // imageView UIImageView *imageView = [[UIImageView alloc] init]; [imageView sd_setImageWithURL:[NSURL URLWithString:self.topic.large_image]]; [scrollView addSubview:imageView]; imageView.cl_width = scrollView.cl_width; imageView.cl_height = self.topic.height * imageView.cl_width / self.topic.width; imageView.cl_x = 0; If (imageView.cl_height >= scrollView.cl_height) {// The image height exceeds the entire screen imageview.cl_y = 0; // ScrollView. contentSize = CGSizeMake(0, imageView.cl_height); } else {// Center imageView.cl_centery = scrollView.cl_height * 0.5; } self.imageView = imageView; CGFloat scale = self.topic.width/imageView.cl_width; If (scale > 1.0) {scrollView. MaximumZoomScale = scale; } } - (IBAction)back { [self dismissViewControllerAnimated:YES completion:nil]; Save {}} - (IBAction) # pragma mark - / / return a scrollView child controls to zoom - viewForZoomingInScrollView: (UIScrollView (UIView *) *)scrollView { return self.imageView; } @endCopy the code

Save pictures to albums

Frame used to save images to albums

#import // iOS9 is deprecated #import // iOS9 is recommendedCopy the code

First look at the contents of the system album




System album

Provides very simple functions for simply saving images to the camera film album in the system.

UIImageWriteToSavedPhotosAlbum(self.imageView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);Copy the code

The SEL method in this function must pass three parameters in a certain format, as explained inside the method




UIImageWriteToSavedPhotosAlbum – API

Access to the system album requires user authorization and will only be requested once. If the user clicks “No”, it will never be allowed to access the album. At this time, the user needs to be reminded to go to [Settings]-[Privacy]-[Photos] to enable it.




Obtaining user Authorization

We want to save the image to the project’s self-created album. In fact, to save the image to the project’s self-created album, we also need to first save the image to the camera film album, and then transfer to the self-created album.

Save images to the steps of creating your own album

1. Check the user authorization status

PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; / / authorization state PHAuthorizationStatusRestricted, because of the parental control, lead to can't method album (has nothing to do with the choice of user) rarely appears. If this state of affairs to remind the user to access photo album system PHAuthorizationStatusDenied, user refused to the current application access album (user first click on the "not allow") if the user refuses, remind consumer to [set] - [private] - [images] in the open. PHAuthorizationStatusAuthorized, the user allows the application to access photo album (user first click on the "good") if the user authorization, Began to save the pictures PHAuthorizationStatusNotDetermined, user is not to make a choice If the user has not yet to make a choice, to the request of the user authorization information, if the user clicks the are not allowed to do nothing, click the began to save the picturesCopy the code

2. Store the pictures in the handed in album 3. Determine whether you have created your own album 4. If already created, get the album that was created, get images, get the request to add images to the album, add images to album 5. If there is no create album, create album request, get Create Album, Get Picture, get Add picture to Album request, Add picture to album

Let’s take a look at the save button click event to save pictures to albums. The frame design is cumbersome to use, but very clever. If you want to make changes to the album, the code must be placed in the block of the performChanges method of PHPhotoLibrary sharedPhotoLibrary. Adding images to the album and creating the album are time-consuming operations. They all execute in child threads. Therefore, if you want to modify the UI during the adding process, such as reminding the user of the success or failure of saving, you need to do it in the main thread.

- (IBAction) save {/ * PHAuthorizationStatusNotDetermined, user PHAuthorizationStatusDenied hasn't made the choice, User refused to the current application access album (user first click on the "not allow") PHAuthorizationStatusAuthorized user allows applications to access photo album current PHAuthorizationStatusRestricted (user first click on the "good"), */ PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; If (status = = PHAuthorizationStatusRestricted) {/ / because of parental control, [SVProgressHUD showErrorWithStatus:@" Unable to access system albums due to system reasons "] } else if (status = = PHAuthorizationStatusDenied) {/ / users to click on the do not allow CLLog (@ "privacy Settings - - - the best may not elder sister xx_cc - allow"); } else if (status = = PHAuthorizationStatusAuthorized) {/ / get a user authorization, save the pictures here [self saveImage]; } else if (status = = PHAuthorizationStatusNotDetermined) {/ / authorized users have not chosen to [PHPhotoLibrary RequestAuthorization :^(PHAuthorizationStatus status) { Save image if (status = = PHAuthorizationStatusAuthorized) {/ / save picture [self saveImage];}}]; }} /** Save the image to the album */ - (void)saveImage {// PHAsset: a resource, such as a picture, a video // PHAssetCollection: __block NSString *assetLocalIdentifier = nil; __block NSString *assetLocalIdentifier = nil; // If you want to modify the "album", The change code must be placed in the block of the performChanges method of [PHPhotoLibrary sharedPhotoLibrary]. // assetLocalIdentifier = [PHAssetCreationRequest creationRequestForAssetFromImage:self.imageView.image].placeholderForCreatedAsset.localIdentifier; } completionHandler:^(BOOL success, NSError * _Nullable error) { If (success == NO) {[self showError:@" PHAssetCollection *createdAssetCollection = [self createdAssetCollection]; If (createdAssetCollection == nil) {// This method is executed in the child thread, so you need to go back to the main thread to modify the UI [self showError:@" Failed to create album! "]; return; } [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{// 3 fetchAssetsWithLocalIdentifiers:@[assetLocalIdentifier] options:nil].lastObject; / / add images to the photo album of requests PHAssetCollectionChangeRequest * request = [PHAssetCollectionChangeRequest ChangeRequestForAssetCollection: createdAssetCollection]; / / add images to the photo album [request addAssets: @ [asset]]. } completionHandler:^(BOOL success, NSError * _Nullable error) { If (success == NO) {[self showError:@" save image successfully!"];;} else {[self showSuccess:@" save image successfully!"];;}}];}]; } /** * Get an album * If the app already has an album, add it directly to the album, If not, create a new album */ - (PHAssetCollection *)createdAssetCollection {// Album name CLAssetCollectionTitle static NSString * CLAssetCollectionTitle = @xx_cc; PHFetchResult *assetCollections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil]; for (PHAssetCollection *assetCollection in assetCollections) { if ([assetCollection.localizedTitle isEqualToString:CLAssetCollectionTitle]) { return assetCollection; }} NSError *error = nil; NSError *error = nil; / / PHAssetCollection logo, this logo can be used to find the corresponding PHAssetCollection object (album object) __block nsstrings * assetCollectionLocalIdentifier = nil; // This method is executed in the main thread thread, [[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{CLAssetCollectionTitle specifies the name of the album assetCollectionLocalIdentifier = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:CLAssetCollectionTitle].placeholderForCreatedAssetCollection.localIdentifier;  } error:&error]; // If (error) return nil; / / get just created album return [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers: @ [assetCollectionLocalIdentifier] options:nil].lastObject; } // Note: Since adding images to the album and creating the album are done in the child thread, UI changes need to go back to the main thread, which is wrapped here. - (void)showSuccess:(NSString *)text { dispatch_async(dispatch_get_main_queue(), ^{ [SVProgressHUD showSuccessWithStatus:text]; }); } - (void)showError:(NSString *)text { dispatch_async(dispatch_get_main_queue(), ^{ [SVProgressHUD showErrorWithStatus:text]; }); }Copy the code

Although saving images to a self-created project album can be tedious, it is basically recyclable once written, and the code above can be used in other projects with minor modifications.

conclusion

Today, I mainly completed some detailed operations inside the cell, including calculating the height of the cell, displaying the content of the cell, viewing pictures and so on. Meanwhile, I reconstructed the essence module to make the structure of the essence module clearer. Take a look at the fifth day results




Day 5 Results

Welcome to point out any mistakes in the article. I am XX_CC, a long grown but not enough of a guy.