巧用TableFooterView設計和實現界面



  • ?設計實現上面的頁面

  • 首先第一個這樣的一個界面如何設計呢?它又有哪些需要注意的細節呢
    • 分析:
      • 可以上下拖動所以是一個scrollview
      • 對于實現可以用tableview來實現
        • 分為2組,每一組的cell為1個
        • 下面的由tableview的tableFooterView來實現
  • 在控制器CYTabbarController.m文件中
    • 1.分組:設置tableview的樣式的為UITableViewStyleGrouped
    • 2.實現tableview數據源方法
      • 返回為2組,每組為1行
      • 給定cell標識,注冊cell
static NSString * const CYMeCellId = @"me";
[self.tableView registerClass:[CYMeCell class] forCellReuseIdentifier:CYMeCellId];
  • tableview定義樣式為UITableViewStyleGrouped分組之后要求:
    • 第一個cell距離頂部間距為10
    • cell之間距離為10
    • 第二個cell與它的tableFooterView之間間距為10
    • 而這個10的間距很常見,所以把它抽成一個宏或者是全局變量為CYCommonMargin
  • 分組樣式的tableview的cell的一些結構:


  • 分析
    • 分組group樣式第一個cell默認距離頂部(第一個cell的Y值)為35,而plain樣式默認是粘著頂部的
    • 分組后的cell擁有一個sectionHeaderHeight和sectionFooterHeight
    • 所以設置每一組的頭部和尾部
    • 設置內邊距(-25代表:所有內容往上移動25)
    self.tableView.sectionHeaderHeight = 0;
    self.tableView.sectionFooterHeight = CYCommonMargin;
    // 設置內邊距(-25代表:所有內容往上移動25)
    self.tableView.contentInset = UIEdgeInsetsMake(CYCommonMargin - 35, 0, 0, 0);

  • 自定義cell---CYMeCell
    • 初始化cell以及重寫它的layoutSubviews:方法(布局cell內部子控件:讓圖片和文字適當排布)
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        self.textLabel.textColor = [UIColor darkGrayColor];

        // 設置背景圖片-圖片可以設置拉伸
        self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"mainCellBackground"]];
    }
    return self;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.imageView.image == nil) return;

    // 調整imageView
    self.imageView.y = CYCommonMargin * 0.5;
    self.imageView.height = self.contentView.height - 2 * self.imageView.y;
    self.imageView.width = self.imageView.height;

    // 調整Label
    //    self.textLabel.x = self.imageView.x + self.imageView.width + CYCommonMargin;
    self.textLabel.x = CGRectGetMaxX(self.imageView.frame) + CYCommonMargin;

    // CGRectGetMaxX(self.imageView.frame) == self.imageView.x + self.imageView.width
    // CGRectGetMinX(self.imageView.frame) == self.imageView.x
    // CGRectGetMidX(self.imageView.frame) == self.imageView.x + self.imageView.width * 0.5
    // CGRectGetMidX(self.imageView.frame) == self.imageView.centerX
}

  • 實現CYMeCell的數據源方法
#pragma mark - 數據源方法
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 2;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CYMeCell *cell = [tableView dequeueReusableCellWithIdentifier:CYMeCellId];

    if (indexPath.section == 0) {
        cell.textLabel.text = @"登錄/注冊";
        cell.imageView.image = [UIImage imageNamed:@"setup-head-default"];
    }else{

        cell.textLabel.text = @"離線下載";
    }

    return cell;
}
  • 以上實現了cell內容的顯示,接下來就是TableFooterView上內容數據的顯示
    • 在PCH文件中定義一個宏,將數據寫到桌面的plist,方便查看

#define CYWriteToPlist(data, filename) [data writeToFile:[NSString stringWithFormat:@"/Users/gecongying/Desktop/%@.plist", filename] atomically:YES];
  • 自定義數據模型CYSquare
  • CYSquare.h文件中

#import <Foundation/Foundation.h>

@interface CYSquare : NSObject
/** 名字 */
@property (nonatomic, copy) NSString *name;
/** 圖標 */
@property (nonatomic, copy) NSString *icon;
/** 鏈接 */
@property (nonatomic, copy) NSString *url;

@end
  • 自定義CYMeFooter
self.tableView.tableFooterView = [[CYMeFooter alloc] init];
  • 在CYMeFooter.m文件中
#import "CYMeFooter.h"
#import <AFNetworking.h>
#import "CYSquare.h"
#import <MJExtension.h>
//#import <UIImageView+WebCache.h>
#import <UIButton+WebCache.h>

@implementation CYMeFooter

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor redColor];

        // 請求參數
        NSMutableDictionary *params = [NSMutableDictionary dictionary];
        params[@"a"] = @"square";
        params[@"c"] = @"topic";

        // 發送請求
        CYWeakSelf;
        [[AFHTTPSessionManager manager] GET:CYRequestURL parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
//            CYWriteToPlist(responseObject, @"square");
            [weakSelf createSquares:[CYSquare objectArrayWithKeyValuesArray:responseObject[@"square_list"]]];
        } failure:^(NSURLSessionDataTask *task, NSError *error) {

        }];
    }
    return self;
}

/**
 * 創建方塊
 */
- (void)createSquares:(NSArray *)squares
{
    // 每行的列數
    int colsCount = 4;

    // 按鈕尺寸
    CGFloat buttonW = self.width / colsCount;
    CGFloat buttonH = buttonW;

    // 遍歷所有的模型
    NSUInteger count = squares.count;
    for (NSUInteger i = 0; i < count; i++) {
        CYSquare *square = squares[i];

        // 創建按鈕
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
        [self addSubview:button];

        // frame
        CGFloat buttonX = (i % colsCount) * buttonW;
        CGFloat buttonY = (i / colsCount) * buttonH;
        button.frame = CGRectMake(buttonX, buttonY, buttonW, buttonH);

        // 數據
        [button setTitle:square.name forState:UIControlStateNormal];


    // 2.如果導入的是#import <UIButton+WebCache.h>
        // 設置按鈕的image
        [button sd_setImageWithURL:[NSURL URLWithString:square.icon] forState:UIControlStateNormal];


    // 1.如果導入的是#import <UIImageView+WebCache.h>
        // 正確做法
//       [[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:square.icon] options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
//        [button setImage:image forState:UIControlStateNormal];
       // 錯誤示范
    //        [button.imageView sd_setImageWithURL:[NSURL URLWithString:square.icon]];
//                }];
    }
}

- (void)buttonClick:(CYSquareButton *)button
{
    CYLogFunc;

}
  • 但是最后的顯示是這樣

  • 所以我們要自定義Button---CYSquareButton
    • 重寫它的initWithFrame:方法和layoutSubviews:方法重新布局子控件
#import "CYSquareButton.h"

@implementation CYSquareButton

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
        self.titleLabel.font = [UIFont systemFontOfSize:14];
        [self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    }
    return self;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.imageView.width = self.width * 0.5;
    self.imageView.height = self.imageView.width;
    self.imageView.y = self.height * 0.1;
    self.imageView.centerX = self.width * 0.5;

    self.titleLabel.width = self.width;
    self.titleLabel.y = CGRectGetMaxY(self.imageView.frame);
    self.titleLabel.x = 0;
    self.titleLabel.height = self.height - self.titleLabel.y;
}

@end
  • 注意在CYMeFooter.m文件中導入相應的頭文件CYSquareButton.h,修改Button為CYSquareButton
  • 最后顯示
  • 但是你會發現有問題要處理

  • 一個控件不能響應點擊事件的原因可能有:
    • 1> userInteractionEnabled = NO;
    • 2> enabled = NO;
    • 3> 父控件的userInteractionEnabled = NO;
    • 4> 父控件的enabled = NO;
    • 5> 控件已經超出父控件的邊框范圍

  • 上面完成后你會發現footerView中的內容無法點擊,更無法正常拖拽(bug)
    • footerView不用設置寬度,默認是填充整個TableView的,永遠跟在TableView后面
    • TableView的拖拽范圍是由它的contentsize決定的
      • 所以我們要讓它的Button可以點擊,內容可以顯示,就得設置好footerView的高度
        • 我們要給它設置高度,但是拖拽是否正常又和設置高度的先后順序有關
        • footerView的高度的設置要在創建它之前設置
        • 因為它是在你設置高度以后,根據你的高度去算出contentsize,去決定能拖拽到哪里。它是取決于你設置那一刻高度是多少
        • 所以上面的問題在于:你的高度設置是在服務器返回數據后才設置的,高度是后面設置的。所以無法影響它現在拖拽上拉的設置
        • 先設置高度,再拿到footerView
  • 我們來設置footerView的高度
  • 第一種方式:先拿到footer的高度,再重新設置footerView
// 設置footer的高度
    self.height = CGRectGetMaxY(button.frame);
            // 重新設置footerView
    UITableView *tableView = (UITableView *)self.superview;
    tableView.tableFooterView = self;
  • 第二種方式:直接改變它的contenSize(內容尺寸)---簡單(推薦)
// 重新設置footerView
    UITableView *tableView = (UITableView *)self.superview;
//    tableView.tableFooterView = self;
    tableView.contentSize = CGSizeMake(0, CGRectGetMaxY(self.frame))
  • 第三種方式:上面兩種方式算高度,我們是拿到最后一個按鈕最大的Y值。還有一種方法:拿到按鈕行數,再乘以按鈕高度也是可以的
    // 設置footer的高度
    NSUInteger rowsCount = count / colsCount;
    if (count % colsCount) { // 不能整除,行數+1
        rowsCount++;
    self.height = rowsCount * buttonH;

    // 重新設置footerView
    UITableView *tableView = (UITableView *)self.superview;
    //    tableView.tableFooterView = self;
    tableView.contentSize = CGSizeMake(0, CGRectGetMaxY(self.frame));
    }
  • 上面這么算,不管整不整除,都可以算出正確的行數
  • 這也可以引出一個公式,將它合并為:
    // 設置footer的高度
     NSUInteger rowsCount = (count + colsCount - 1) / colsCount;
    self.height = rowsCount * buttonH;

    // 重新設置footerView
    UITableView *tableView = (UITableView *)self.superview;
    //    tableView.tableFooterView = self;
    tableView.contentSize = CGSizeMake(0, CGRectGetMaxY(self.frame));
  • 這個公式?用到一個地方很有作用:分頁
    • 就像百度上搜索“美女”,給你返回一個結果,但是結果數據這么多,不可能一頁就能顯示完,所以采用分頁顯示數據??偣灿卸嗌贄l數據,每一頁有固定條,總共有多少頁
    • 算是萬能公式,遇到相關需求,就不需要再苦逼的進行判斷了
     總頁數 == (總個數 + 每頁的個數 - 1) / 每頁的個數

     總個數:97
     每頁的個數:17
     總頁數 =  (97 + 17 - 1) / 17


  • 現在按鈕可以點擊,界面可以正常拖拽。下面做一下分割線
    • 第一種方式:背景設為白色,添加UIView(太多不推薦)--加一個View
    • 第二種方式:在按鈕原有算好的基礎上,讓他們的高度和寬帶都減去1(記得要先算好,再減1)--空出間隙
button.frame = CGRectMake(buttonX, buttonY, buttonW-1, buttonH-1);
  • 第三種方式:?看你在公司和美工的關系了??
    • 直接讓美工做一個有邊線的按鈕背景圖片
[self setBackgroundImage:[UIImage imageNamed:@"mainCellBackground"] forState:UIControlStateNormal];

  • 接下來,監聽按鈕的點擊,拿到按鈕索引對應方塊中的數據
  • 可以看出一個SquareButton對應一個Square模型
    • 所以在SquareButton.h文件中加一個屬性
#import <UIKit/UIKit.h>

@class CYSquare;

@interface CYSquareButton : UIButton
/** 方塊模型 */
@property (nonatomic, strong) CYSquare *square;
@end

  • 在CYSquareButton.m文件中重寫setSquare:方法,將數據請求封裝起來
- (void)setSquare:(CYSquare *)square
{
    _square = square;

    // 數據
    [self setTitle:square.name forState:UIControlStateNormal];
    // 設置按鈕的image
    [self sd_setImageWithURL:[NSURL URLWithString:square.icon] forState:UIControlStateNormal];
}
// 1.創建按鈕
// 2.設置按鈕frame
// 3.設置模型數據(拿到模型,把模型數據拆分給子控件)
button.square = squares[i];
  • 這樣的話,我拿到按鈕就相當于拿到模型,一個按鈕對應一個模型(更有封裝性)
    • 今后當我們碰到一對一的情況的時候,就是一個數據對應一個控件的時候。你就要想到可不可以給這個控件或者是這個View綁一個模型
    • 當然一對一也可以用索引, 當我們控件的索引和我們模型的索引是一致的時候,先通過控件取出索引,再通過索引取出模型數據
    • 針對一對一我們還可以用字典,假如按鈕能做一個key,通過按鈕的key可以取出模型對應的value

  • 現在我們要通過url去打開一個下一個界面CYWebViewController(新建并繼承于UIViewController[里面有一個WebView和工具條])
  • 我們選擇push過去,在CYMeFooter.m文件中
- (void)buttonClick:(CYSquareButton *)button
{
    // 拿到以http開頭的URL
    if ([button.square.url hasPrefix:@"http"] == NO) return;
    CYWebViewController *webVc = [[CYWebViewController alloc] init];
    webVc.square = button.square;

    // 取出當前選中的導航控制器
    UITabBarController *rootVc = (UITabBarController *)self.window.rootViewController;
    UINavigationController *nav = (UINavigationController *)rootVc.selectedViewController;
    [nav pushViewController:webVc animated:YES];

}
  • 上面的CYMeFooter是一個View,你是沒法拿到導航控制器的,而且View里面是沒有屬性拿到它對應的控制器的,那我們怎么辦呢?
    • 但是有一個控制器是哪里都能拿到的,就是窗口的根控制器
    • 而我們這個窗口的根控制器本質是一個CYTabBarController
    • 而“我”“精華”“新帖”“關注”對應的導航控制器其實都是CYTabBarController的子控制器
    • 但是我們要push過去,拿到導航控制器,但是這里有4個導航控制器,,所以我們要拿到對應的被選中的“我”對應導航控制器
  • 所以今后得注意:不要一要push跳轉就:self.NavigationController push...而是要找到對應的導航控制器,再做動作

  • 進入那個界面,顯示一個網頁,意味著你要把URL傳給它

  • 上面還得顯示一個標題,你顯示標題的文字最好也要和你按鈕顯示的文字一致

  • 也就是說要傳兩個東西--文字和URL。既然要傳兩個,干脆就把模型傳過去算了

  • 在CYWebViewController.h文件中傳入模型

#import <UIKit/UIKit.h>

@class CYSquare;

@interface CYWebViewController : UIViewController
/** 方塊 */
@property (nonatomic, strong) CYSquare *square;
@end
  • 然后再來界面布局:
  • 用Xib先把底部工具條布置好,剩下的就是上面的網頁界面
    • 底部工具條可以搞個UIView,上面搞幾個Button
    • 或者搞個ToolBar
  • 控制按鈕的行為,在CYWebViewController.m文件中
#import "CYWebViewController.h"
#import "CYSquare.h"

@interface CYWebViewController () <UIWebViewDelegate>
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *backItem;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *forwardItem;
@end

@implementation CYWebViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.title = self.square.name;
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.square.url]]];

    self.webView.backgroundColor = CYCommonBgColor;
    // 為了讓webView的內容能完整顯示,我們可以設置它的contentInset向下挪64
    // 但是webView是繼承于UIView的,是無法設置它的contentInset的。但是它能滾動,是因為它里面鑲嵌了一個ScrollView的屬性
    // NS_CLASS_AVAILABLE_IOS(2_0) __TVOS_PROHIBITED @interface UIWebView : UIView <NSCoding, UIScrollViewDelegate>
    self.webView.scrollView.contentInset = UIEdgeInsetsMake(64, 0, 0, 0);
}

- (IBAction)back {
    [self.webView goBack];
}

- (IBAction)forward {
    [self.webView goForward];
}

- (IBAction)refresh {
    [self.webView reload];
}

#pragma mark - <UIWebViewDelegate>
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    self.backItem.enabled = webView.canGoBack;
    self.forwardItem.enabled = webView.canGoForward;
}
@end
  • 為了讓webView的內容能完整顯示,我們可以設置它的contentInset向下挪64
  • 但是webView是繼承于UIView的,是無法設置它的contentInset的。但是它能滾動,是因為它里面鑲嵌了一個ScrollView的屬性
  • NS_CLASS_AVAILABLE_IOS(2_0) __TVOS_PROHIBITED @interface UIWebView : UIView <NSCoding,
  • 前進和后退按鈕的設置
#pragma mark - <UIWebViewDelegate>
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    self.backItem.enabled = webView.canGoBack;
    self.forwardItem.enabled = webView.canGoForward;
}
  • 擴展:有關于webView網頁加載進度條
    • iOS中只有蘋果的Safari進度條加載才是真的,其它的瀏覽器(UC,百度等)進度條都是假的
    • 因為蘋果內部是沒有提供進度條的接口給外界,就算提供了也是私有的,也就是說只有蘋果官方內部才可以用,如果你用了蘋果監聽進度的東西的話,你的App是不能上線的
    • 那么怎么做呢?你會發現像百度和UC這樣的瀏覽器在加載時,進度顯示會不停的加載,有時候網速不好,它也加載,加載到后面,你以為要加載完了,它就是不動,加載完,瞬間就上去了
    • 有些進度條加載是做得很逼真的,我們也可以做
      • 用一個UIView,高度為1或者2,加一個定時器,給一個時間,每隔1秒鐘,讓它的寬度不斷的加。如果網速不行,就一直加載,一直加載,加到90%的時候,先停住不動,等網速來了,webViewDidFinishLoad:網頁加載完后,讓它的寬度等于屏幕的寬度,給它一個動畫,讓它過去,最后讓它消失就可以了。
      • 在github上有一個框架,可以用它,也可以根據它的思路自己寫一下


https://github.com/Tuberose621/-TableFooterView-
如果可以的話,Give me a star!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容