《iOS編程(第四版)》Demo:Homeowner

功能:使用 UITableView 實現(xiàn)一個名為 Homepwner 的應(yīng)用,用來管理財產(chǎn)清單,通過 UITableView 對象顯示一組 BNRItem 對象,實現(xiàn)表格行的添加、刪除和移動操作。

要點:UITableView

關(guān)于創(chuàng)建 Empty Application 空應(yīng)用模板

很多老版的 iOS 入門教程會在創(chuàng)建新項目時使用 Empty Application 模板,因為空應(yīng)用模板幾乎沒有多余的代碼,而其他模板會生成很多通用的代碼。這些代碼雖然能幫助開發(fā)應(yīng)用,但是對于初學(xué)者弊大于利。

而蘋果在 Xcode6 開始就移除了 Empty Application 模板,因此我們無法直接創(chuàng)建 Empty Application 模板,但是可以通過先創(chuàng)建一個 Single View Application 模板,再修改一下就可以達(dá)到此目的:

  1. Xcode 中創(chuàng)建一個 Single View Application 模板;

  2. 刪除項目中的 Main.storyboardLaunchScreen.storyboard 這兩個 XIB 文件(鼠標(biāo)選中并右擊Delete);

  3. info.plist 配置文件中刪除 Launch screen interface file base nameMain storyboard file base name 這兩項(選中該行,鼠標(biāo)點擊中間的灰白色減號按鈕)

  4. 打開 AppDelegate.m 文件,在委托方法 application:didFinishLaunchingWithOptions: 中修改如下:

    Objective-C:

    // 在此之前需要先導(dǎo)入根視圖控制器的頭文件: #import "ViewController.h"
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // 創(chuàng)建 UIWindow 對象
        self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        // 設(shè)置 UIWindow 對象的根視圖控制器
        ViewController *viewController = [[ViewController alloc] init];
        self.window.rootViewController = viewController;
        // 設(shè)置窗口背景色為白色
        self.window.backgroundColor = [UIColor whiteColor];
        // 設(shè)置窗口可見
        [self.window makeKeyAndVisible];
        return YES;
    }
    

    Swift 3:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool 
    {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        self.window?.backgroundColor = UIColor.white
        self.window?.makeKeyAndVisible()
        return true
    }
    

參考:

(一)TableView

MVC設(shè)計模式

MVC(Model-View-Controller) 是模型-視圖-控制器設(shè)計模式。其含義是,應(yīng)用創(chuàng)建的任何一個對象,其類型必定是以下三種類型中的一種:

  • 模型:負(fù)責(zé)存儲數(shù)據(jù),與用戶界面無關(guān)。
  • 視圖:負(fù)責(zé)顯示界面,與模型對象無關(guān)。
  • 控制器:負(fù)責(zé)確保視圖對象和模型對象的數(shù)據(jù)保持一致。

UITableViewController

視圖控制對象:該應(yīng)用采用 MVC 的設(shè)計模式,UITableView 是視圖,因此要通過視圖控制對象來創(chuàng)建和釋放 UITableView 視圖對象,并負(fù)責(zé)顯示或隱藏視圖。

數(shù)據(jù)源UITableView 對象要有數(shù)據(jù)源才能正常工作。UITableView 對象會向數(shù)據(jù)源查詢要顯示的行數(shù)、顯示表格行所需要的數(shù)據(jù)和其他所需的數(shù)據(jù)。沒有數(shù)據(jù)的 UITableView 對象只是空殼。凡是遵守 <UITableViewDataSource> 協(xié)議的 Objective-C 對象,都可以成為 UITableView 對象的數(shù)據(jù)源(即dataSource 屬性所指向的對象)。

委托對象:還要為 UITableView 對象設(shè)置委托對象,以便能在該對象發(fā)生特定事件時做出相應(yīng)的處理。凡是遵守 <UITableViewDelegate> 協(xié)議的對象,都可以成為 UITableView 對象的委托對象。

UITableViewController 對象可以扮演以上全部角色,包括 視圖控制對象數(shù)據(jù)源委托對象

UITableViewController 對象是 UIViewController 的子類,所以也有 view 屬性。 UITableViewController 對象的 view 屬性指向一個 UITableView 對象,并且這個 UITableView 對象由 UITableViewController 對象負(fù)責(zé)設(shè)置和顯示。 UITableViewController 對象會在創(chuàng)建 UlTableView 對象后,為這個 UITableView 對象的 dataSourcedelegate 賦值,并指向自己。

Homepwner對象圖

1. 創(chuàng)建 UITableViewController 子類:HQLItemsViewController

#import <UIKit/UIKit.h>

@interface HQLItemsViewController : UITableViewController

@end

Tips: 如果你創(chuàng)建了一個 UITableViewController 的子類對象,那么就不需要再顯式地聲明該對象需要遵守 dataSourcedelegate 協(xié)議了,因為它是默認(rèn)遵守的,你只需要去實現(xiàn)協(xié)議方法即可。

self.tableView.dataSource = self;
self.tableView.delegate   = self;

2. 覆蓋父類的指定初始化方法 initWithStyle:,將指定初始化方法改為init:

// 1?? 在【新的指定初始化方法】中調(diào)用父類的指定初始化方法;
-(instancetype) init {
    //調(diào)用父類的指定初始化方法
    self = [super initWithStyle:UITableViewStylePlain];
    return self;
}

// 2?? 覆蓋父類的指定初始化方法,調(diào)用【新的指定初始化方法】。
- (instancetype) initWithStyle:(UITableViewStyle)style {
    return [self init];
}

HQLItemsViewController.m 文件中實現(xiàn)以上兩個初始化方法后,可以確保無論向新創(chuàng)建的 HQLItemsViewController 對象發(fā)送哪一個初始化方法,初始化后的對象都會使用 UITableViewStylePlain 風(fēng)格。

3.創(chuàng)建 HQLItemsViewController 對象

AppDelegate.m 文件中導(dǎo)入 HQLItemsViewController.h 文件并初始化創(chuàng)建 HQLItemsViewController 對象。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // 創(chuàng)建TableView視圖控制器
    HQLItemsViewController *itemsViewController = [[HQLItemsViewController alloc] init];
    self.window.rootViewController = itemsViewController;
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

4.為UITableView 對象設(shè)置內(nèi)容

這里從外部導(dǎo)入了一個寫好的 HQLItem 類的頭文件和實現(xiàn)文件(HQLItem.h 和 HQLItem.m),該類用于生成一組隨機(jī)數(shù)據(jù)。

//
//  HQLItem.h
//  2.1 RandomItems
//
//
/**
 *  該對象表示某人在真實世界擁有的一件物品
 *
 */
#import <Foundation/Foundation.h>

// 頭文件聲明順序:實例變量、類方法、初始化方法、其他方法
@interface Item : NSObject

// 名稱
@property (nonatomic, copy) NSString *itemName;
// 序列號
@property (nonatomic, copy) NSString *serialNumber;
// 價值
@property (nonatomic) int valueInDollars;
// 創(chuàng)建日期
@property (nonatomic, readonly, strong) NSDate *dateCreated;
// 照片的key
@property (nonatomic, copy) NSString *itemKey;

//類方法
+ (instancetype)randomItem;

// Item類的指定初始化方法
// instancetype,該關(guān)鍵字表示的返回值類型和調(diào)用方法的類型相同,
// init方法的返回值類型都聲明為instancetype
- (instancetype)initWithItemName:(NSString *)name
                  valueInDollars:(int)value
                    serialNumber:(NSString *)sNumber;

// 其他初始化方法
- (instancetype)initwithName:(NSString *)name serialNumber:(NSString *)sNumber;
- (instancetype)initWithItemName:(NSString *)name;

@end


//
//  HQLItem.m
//  2.1 RandomItems
//
//

// #import 可以確保不會重復(fù)導(dǎo)入同一個文件
#import "Item.h"

@implementation Item

// 類方法
+ (instancetype)randomItem{
    
    //創(chuàng)建不可變數(shù)組對象,包含三個形容詞
    NSArray *randomAdjectiveList = @[@"Fluffy",@"Rusty",@"Shiny"];
    
    //創(chuàng)建三個不可變數(shù)組對象,包含三個名詞
    NSArray *randomNounList = @[@"Bear",@"Spark",@"Mac"];
    
    //根據(jù)數(shù)組對象所含對象的個數(shù),得到隨機(jī)索引
    //注意:運(yùn)算符%是模運(yùn)算符,運(yùn)算后得到的是余數(shù)
    //因此 adjectiveIndex 是一個0到2(包括2)的隨機(jī)數(shù)
    NSInteger adjectiveIndex = arc4random() % [randomAdjectiveList count];
    NSInteger nounIndex = arc4random() % [randomNounList count];
    
    NSString *randomName = [NSString stringWithFormat:@"%@%@",
                            randomAdjectiveList [adjectiveIndex],
                            randomNounList [nounIndex]];
    
    
    int randomValue = arc4random() % 100;
    
    NSString *randomSerialNumber = [NSString stringWithFormat:@"%C%C%C%C%C",
                                    (unichar)('0'+arc4random() % 10),
                                    (unichar)('A'+arc4random() % 26),
                                    (unichar)('0'+arc4random() % 10),
                                    (unichar)('A'+arc4random() % 26),
                                    (unichar)('0'+arc4random() % 10)];
    
    Item *newItem = [[self alloc] initWithItemName:randomName
                                    valueInDollars:randomValue
                                      serialNumber:randomSerialNumber];
    
    return newItem;
}


// 串聯(lián)(chain)使用初始化方法
- (instancetype)initWithItemName:(NSString *)name
                  valueInDollars:(int)value
                    serialNumber:(NSString *)sNumber {
    
    self = [super init];
    //if(self):父類的指定初始化方法是否成功創(chuàng)建了父類對象?
    if(self){
        _itemName       = name;
        _serialNumber   = sNumber;
        _valueInDollars = value;
        // 設(shè)置_dateCreated的值為系統(tǒng)當(dāng)前時間
        _dateCreated    = [[NSDate alloc] init];
        // 創(chuàng)建一個 NSUUID 對象,然后獲取其 NSString 類型的值
        NSUUID *uuid = [[NSUUID alloc] init];
        NSString *key = [uuid UUIDString];
        _itemKey = key;
    }
    //返回初始化后的對象的新地址
    return self;
}

- (instancetype)initwithName:(NSString *)name
                serialNumber:(NSString *)sNumber {
    return [self initWithItemName:name
                   valueInDollars:0
                     serialNumber:sNumber];
    
}

- (instancetype)initWithItemName:(NSString *)name {
    // 調(diào)用指定初始化方法
    return [self initWithItemName:name
                   valueInDollars:0
                     serialNumber:@""];
}

- (instancetype)init {
    return [self initWithItemName:@"Item"];
}

// 覆寫 description 方法
// %@,對應(yīng)的實參類型是指向任何一種對象的指針,首先返回的是該實參的description消息
- (NSString *)description {
    NSString *descriptionString =
        [[NSString alloc] initWithFormat:@"%@(%@): ,Worth $%d ,recorded on %@",
                                        self.itemName,
                                        self.serialNumber,
                                        self.valueInDollars,
                                        self.dateCreated ];
    return descriptionString;
}


#pragma mark - NSCoding

- (void) encodeWithCoder:(NSCoder *)aCoder {
    
    [aCoder encodeObject :self.itemName       forKey      :@"itemName"];
    [aCoder encodeObject :self.serialNumber   forKey  :@"serialNumber"];
    [aCoder encodeObject :self.dateCreated    forKey   :@"dateCreated"];
    [aCoder encodeInt    :self.valueInDollars forKey:@"valueInDollars"];
}

- (instancetype) initWithCoder:(NSCoder *)aDecoder {
    
    self = [super init];
    if (self) {
        _itemName       = [aDecoder decodeObjectForKey     :@"itemName"];
        _serialNumber   = [aDecoder decodeObjectForKey :@"serialNumber"];
        _dateCreated    = [aDecoder decodeObjectForKey  :@"dateCreated"];
        _valueInDollars = [aDecoder decodeIntForKey  :@"valueInDollars"];
    }
    return self;
}

@end

UITableView 數(shù)據(jù)源

  • Cocoa Touch 中,UITableView 對象會自己查詢另一個對象以獲得需要顯示的內(nèi)容,這個對象就是 UITableView 對象的數(shù)據(jù)源,也就是 dataSource 屬性所指向的對象。
  • 該應(yīng)用中,UITableView 對象的數(shù)據(jù)源就是 HQLItemsViewController 對象自己。所以要為 HQLItemsViewController 對象添加相應(yīng)的屬性和方法,使其能夠保存多個 HQLItem 對象。
  • 使用 HQLItemStore (類型為NSMutableArray)對象來負(fù)責(zé)保存和加載 HQLItem 對象,當(dāng)某個對象需要訪問所有的 HQLItem 時,可以通過 HQLItemStoreallItems 方法獲取包含所有 HQLItemNSMutableArray。此外,HQLItemStore 還會負(fù)責(zé)將 HQLItem 存入文件,或者從文件重新載入。

5.創(chuàng)建 HQLItemStore

  • HQLItemStore 對象是一個單例對象。也就是說,每個應(yīng)用只會有一個這種類型的對象。如果應(yīng)用嘗試創(chuàng)建另一個對象,HQLItemStore 類就會返回已經(jīng)存在的那個對象。
#import <Foundation/Foundation.h>

@interface HQLItemStore : NSObject

//將此類設(shè)置為單例對象
+ (instancetype)sharedStore;

@end
  • HQLItemStore.m 中實現(xiàn) sharedStore 單例方法,同時編寫一個拋出異常的 init 方法和私有指定初始化方法 initPrivate
@implementation HQLItemStore

+ (instancetype)sharedStore {
    
    //將sharedStore聲明為了靜態(tài)變量,當(dāng)某個定義了靜態(tài)變量的方法返回時,程序不會釋放相應(yīng)的變量
    static HQLItemStore *sharedStore = nil;
    
    //判斷是否需要創(chuàng)建一個sharedStore對象
    // (! sharedStore) 為真 ,即(sharedStore)為假,不存在
    if (! sharedStore) {
        sharedStore = [[self alloc] initPrivate];
    }
    return sharedStore;
    
}

// 如果誤調(diào)用了 [[HQLItemstore alloc] init],就提示應(yīng)該使用 [HQLItemstore sharedStore]。
- (instancetype)init {
    @throw [NSException exceptionWithName:@"Singleton"
                                   reason:@"Use + [HQLItemStore sharedStore]"
                                 userInfo:nil];
    return  nil;
}

// 這是真正的(私有的)初始化方法
- (instancetype)initPrivate {
    self = [super init];  
    return self; 
}
  • HQLItemStore.h 中聲明一個方法和一個屬性,分別用于創(chuàng)建和保存 HQLItem 對象。
#import <Foundation/Foundation.h>

//@class 只需要使用類的聲明,無需知道具體的實現(xiàn)細(xì)節(jié)
@class HQLItem;

@interface HQLItemStore : NSObject

//保存 HQLItem
//allItems屬性被聲明為NSArray(不可變數(shù)組),且設(shè)置為readonly,這樣其他類既無法將一個新的數(shù)組賦給allItems,也無法修改allItems
//allItems屬性對外部公開使用
@property (nonatomic, readonly) NSArray *allItems;

+ (instancetype)sharedStore;

//創(chuàng)建 HQLItem
- (HQLItem *)createItem;

@end
  • HQLItemStore.m 頂部導(dǎo)入 HQLItem.h 文件,以便之后向 HQLItem.h 對象發(fā)送消息。
  • 接下來在 HQLItemStore.m 的類擴(kuò)展中聲明一個可變數(shù)組。
#import "HQLItemStore.h"
#import "HQLItem.h"

@interface HQLItemStore ()

// 類擴(kuò)展中為NSMutableArray(可變數(shù)組)
//privateItems屬性只在內(nèi)部使用
@property (nonatomic) NSMutableArray *privateItems;

@end

@implementation HQLItemStore
...
  • HQLItemStore.m 中實現(xiàn) initPrivate 方法,初始化 privateItem 屬性。同時還需要覆蓋 allItem 的取方法,返回 privateItems,同時實現(xiàn) createItem 方法。
- (instancetype)initPrivate {
    self = [super init];
    //父類的init方法是否成功創(chuàng)建了對象
    if (self) {
        _privateItems = [[NSMutableArray alloc] init];
    }
    
   return self; 
}

- (Item *)createItem {
    
    HQLItem *item = [HQLItem randomItem];
    
    [self.privateItems addObject:item];
    
    return item;
}

//allItems取方法,返回值為NSArray類型
- (NSArray *)allItems {
    
   //方法體中返回值為NSMutableArray類型
   return self.privateItems;
}

6.實現(xiàn)數(shù)據(jù)源方法

HQLItemsViewController.m 頂部導(dǎo)入 HQLItemStore.hHQLItem.h,然后更新指定初始化方法,創(chuàng)建 5 個隨機(jī)的 HQLItem 對象并加入 HQLItemStore對象。

-(instancetype) init {
    //調(diào)用父類的指定初始化方法
    self = [super initWithStyle:UITableViewStyleGrouped];
    //初始化生成隨機(jī)對象
    if (self) {
        for (int i = 0; i < 5; i ++) {
            [[HQLItemStore sharedStore] createItem];
        }
    }
    return self;
}

QLItemViewController.m 中實現(xiàn)數(shù)據(jù)源協(xié)議 tableView: numberOfRowsInSection: 方法

//返回應(yīng)該顯示的行數(shù)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return [[[HQLItemStore sharedStore] allItems] count];
}

另一個必須要實現(xiàn)的數(shù)據(jù)源協(xié)議是 tableView: cellForRowAtIndexPath:

注:實現(xiàn)該方法還涉及到另一個類:UITableViewCell,此類的詳解及創(chuàng)建自定義子類日后分析

//獲取用于顯示第section個表格段、第row行數(shù)據(jù)的UITableViewCell對象
//返回各行所需視圖,每個表格段對應(yīng)一組獨立的行
- (UITableViewCell *)tableView:(UITableView *)tableView
     cellForRowAtIndexPath:(NSIndexPath *)indexPath {

//    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"];

//由協(xié)議獲取已經(jīng)分配的單元格,而不是分配一個新的,
//創(chuàng)建或重用UITableViewCell對象
//按照約定,應(yīng)該將UITableViewCell或者UITableViewCell子類的類名用作reuseIdentifier。
UITableViewCell *cell = [tableView
    dequeueReusableCellWithIdentifier:@"UITableViewCell"
                         forIndexPath:indexPath];

//獲取allItem的第n個 HQLItem 對象,這里的n是該UITableViewCell對象所對應(yīng)的表格行索引
//然后將該Item對象的描述信息賦給UITableViewCell對象的textlabel
NSArray *items = [[HQLItemStore sharedStore] allItems];
HQLItem *item = items[indexPath.row];
cell.textLabel.text = [item description];
return cell;

}

7. 重用 UITableViewCell 對象

UITableView 對象會將移出窗口的 UITableViewCell 對象放入UITableViewCell 對象池,等待重用。當(dāng) UITableView 對象要求數(shù)據(jù)源返回某個 UITableViewCell 對象時,數(shù)據(jù)源可以先查看對象池。如果有未使用的UITableViewCell 對象,就可以用新的數(shù)據(jù)配置這個 UITableViewCell 對象,然后將其返回給 UITableView 對象,從而避免創(chuàng)建新對象。同時,為了重用 UITableViewCell 對象,需要將創(chuàng)建UITableViewCell 對象的過程交由系統(tǒng)管理,如果對象池中沒有 UITableViewCell 對象,則由系統(tǒng)初始化創(chuàng)建所需類型的 UITableViewCell 對象。

- (void)viewDidLoad {
    [super viewDidLoad];

    // 重用 UITableViewCell,向表視圖注冊應(yīng)該使用的 UITableViewCell 類型
    [self.tableView registerClass:[UITableViewCell class]       
           forCellReuseIdentifier:@"UITableViewCell"];
}

8. 編輯模式

在編輯模式下,用戶可以管理 UITableView 中的表格行,例如添加、刪除和移動等操作。

為應(yīng)用添加編輯模式的界面有兩種方式:
1?? 在視圖控制器頂層添加 NavigationController
2?? 為 UITableView 對象添加表頭視圖。

方法一:在視圖控制器頂層添加導(dǎo)航視圖控制器

  1. 將導(dǎo)航視圖控制器設(shè)置為根視圖控制器

AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // 創(chuàng)建 HQLItemsViewController 對象
    HQLItemsViewController *itemsViewController = [[HQLItemsViewController alloc] init];
    // 創(chuàng)建 UINavigationController 對象
    // 將 HQLItemsViewController 對象設(shè)置為 UINavigationController 對象的根視圖控制器
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:itemsViewController];
    // 將 UINavigationController 對象設(shè)置為 UIWindow 對象的根視圖控制器
    self.window.rootViewController = navigationController;
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
  1. 在 HQLItemsViewController.m 中設(shè)置導(dǎo)航欄標(biāo)題和按鈕
-(instancetype) init {
    // 調(diào)用父類的指定初始化方法
    self = [super initWithStyle:UITableViewStylePlain];
    // 初始化生成隨機(jī)對象
    if (self) {
         
        // 設(shè)置導(dǎo)航欄標(biāo)題
        UINavigationItem *navItem = self.navigationItem;
        navItem.title = @"Homepwner";
        
        // 為導(dǎo)航欄設(shè)置【添加】和【編輯】按鈕,以替換表頭視圖(headerView)
        // 創(chuàng)建新的 UIBarButtonItem 對象
        // 將其目標(biāo)對象設(shè)置為當(dāng)前對象,將其動作方法設(shè)置為 addNewItem
        UIBarButtonItem *bbi =  [[UIBarButtonItem alloc]
                        initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
                                             target:self
                                             action:@selector(addNewItem:)];
        // 為 UINavigationItem 對象的 rightBarButtonItem 屬性賦值,
        // 指向新創(chuàng)建的 UIBarButtonItem 對象
        navItem.rightBarButtonItem = bbi;
        
        // 為 UINavigationBar 對象添加編輯按鈕
        navItem.leftBarButtonItem = self.editButtonItem;
    }
    return self;
}

方法二:為 UITableView 對象添加表頭視圖

  1. 創(chuàng)建一個針對表格的表頭視圖

創(chuàng)建一個新的 XIB 文件。cmd+N -> 在 User Interface 窗口中選擇 Empty,將文件名設(shè)置為 HeaderView.xib 并保存。打開 Interface Builder 后,先拖拽一個UIView 對象至畫布,再添加兩個 UIButton 對象。

?? 注意一定要選擇 Empty 類別的XIB文件,有一次我錯選了 View,編譯運(yùn)行測試就是加載不出視圖來,老糾結(jié)了??

選中File's Owner,修改Class文本框為 HQLItemsViewController

接著選中 UIView 對象,將 Size 屬性設(shè)置為 Freeform 以調(diào)整視圖對象大小 。

UIView 對象的背景顏色改為全透明顏色,即 ClearColor.

  1. HQLItemViewController.m 的類擴(kuò)展中聲明插座變量 headerView,并添加兩個動作方法。
@interface HQLItemsViewController ()

// 載入XIB文件后,headerView會指向XIB文件中的頂層對象,并且是強(qiáng)引用。
// ?? 指向頂層對象的插座變量必須聲明為強(qiáng)引用;相反,當(dāng)插座變量指向頂層對象所擁有的對象(例如頂層對象的子視圖時),應(yīng)該使用弱引用。
@property (nonatomic,strong) IBOutlet UIView *headerView;

@end

@implementation HQLItemsViewController

// ...

#pragma mark 表頭視圖按鈕

// 添加新項目
- (IBAction)addNewItem:(id)sender {
    // 創(chuàng)建新的 Item 對象并將其加入 HQLItemStore 對象
    Item *newItem = [[HQLItemStore sharedStore] createItem];
    // 獲取新創(chuàng)建的對象在 allItem 數(shù)組中的索引
    NSInteger lastRow = [[[HQLItemStore sharedStore] allItems] indexOfObject:newItem];
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:lastRow
                                                inSection:0];
    // 將新行插入UITableview對象
    [self.tableView insertRowsAtIndexPaths:@[indexPath]
                          withRowAnimation:UITableViewRowAnimationTop];
}

// 切換編輯模式
- (IBAction)toggleEditingMode:(id)sender {
    // 如果當(dāng)前的視圖控制對象已經(jīng)處在編輯模式
    if (self.isEditing) {
        // 修改按鈕文字,提示用戶當(dāng)前的表格狀態(tài)
        [sender setTitle:@"Edit" forState:UIControlStateNormal];
        // 關(guān)閉編輯模式
        [self setEditing:NO animated:YES];
    }else {
        // 修改按鈕文字,提示用戶當(dāng)前的表格狀態(tài)
        [sender setTitle:@"Done" forState:UIControlStateNormal];
        // 開啟編輯模式
        [self setEditing:YES animated:YES];
    }
}
  1. HQLItemsViewController.m 中使用 Lazy Loading 方式實現(xiàn) hearerViewgetter 方法,載入應(yīng)用程序包中的 XIB 文件。
// 載入headerView.xib文件
- (UIView *)headerView {
    
    // 如果還沒有載入headerView
    if (!_headerView) {
        
        /* 載入指定的XIB文件
         *
         * 將 self 作為 owner 實參(擁有者)傳給 NSBundle 對象,
         * 目的是當(dāng) HQLItemsViewController 將XIB文件加載為NIB文件時,
         * 使用 HQLItemsViewController 對象自身替換占位符對象 File's Owner
         *
         */
        [[NSBundle mainBundle] loadNibNamed:@"HeaderView"
                                      owner:self
                                    options:nil];
    }
    return _headerView;
}
  1. headerView 設(shè)置為 UITableView 對象的表頭視圖。在HQLItemsViewController.mViewDidLoad 方法中添加以下代碼:
// 加載headerView,并將其設(shè)置為UITableView對象的表頭視圖
UIView *header = self.headerView;
[self.tableView setTableHeaderView:header];

9. 增加行

該應(yīng)用中,增加行的實現(xiàn)方式是,在表視圖上放置添加按鈕,點擊添加按鈕之后系統(tǒng)調(diào)用 createItem 方法創(chuàng)建隨機(jī)對象.

10. 刪除行

如果 UITableView 對象請求確認(rèn)的是刪除操作,刪除 Homepwner 中的某個表格行(即 UITableViewCell 對象)步驟:

  1. 刪除視圖。從 UITableView 對象刪除指定的 UITableViewCell 對象;
  2. 刪除模型。找到和需要刪除的 UITableViewCell 對象對應(yīng)的 HQLItem 對象,也將其從HQLItemStore 中刪除。
    ?
    完成第 2 步需要在 HQLItemStore.h 中增加一個刪除方法 removeItem,用于移除指定的 HQLItem 對象,接著在 HQLItemStore.m 文件中實現(xiàn)該方法。

NSMutableArray 中的刪除方法:

  • removeItem 方法調(diào)用了 NSMutableArray 中的 removeObjectIdenticalTo: 比較指向?qū)ο蟮闹羔槪摲椒ㄖ粫瞥龜?shù)組所保存的那些和傳入對象指針完全相同的指針。
  • removeObject: 該方法會枚舉數(shù)組,向每一個對象發(fā)送 isEqual: 消息,判斷當(dāng)前對象和傳入對象所包含的數(shù)據(jù)是否相等。
- (void)removeItem:(Item *)item {
[self.privateItems removeObjectIdenticalTo:item];
}

接下來為 HQLItemViewController 實現(xiàn)方法 tableView:commitEditingStyle:forRowAtIndexPath:

- (void)tableView:(UITableView *)tableView  //發(fā)送該消息的UITableView對象
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle    //編輯風(fēng)格
forRowAtIndexPath:(NSIndexPath *)indexPath {    //相應(yīng)表格行所在的表格段索引和行索引

//如果UITableView對象請求確認(rèn)的是刪除操作
if (editingStyle ==UITableViewCellEditingStyleDelete) {
    
    //先刪除Item對象
    NSArray *items = [[HQLItemStore sharedStore] allItems];
    Item *deleteItem = items [indexPath.row];
    [[HQLItemStore sharedStore] removeItem:deleteItem];
    
    //還要刪除表格視圖中的相應(yīng)表格行(帶動畫效果)
    [tableView deleteRowsAtIndexPaths:@[indexPath]
                     withRowAnimation:UITableViewRowAnimationFade];
}
}

11. 更改刪除按鈕的標(biāo)題文本

刪除 UITableView 對象中的某個表格行時,相應(yīng)的 UITableViewCell 對象會在其右側(cè)顯示一個標(biāo)題為“Delete”的按鈕,先將該按鈕標(biāo)題改為中文“刪除”。

- (NSString *)tableView:(UITableView *)tableView
titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath {
    return  @"刪除";
}

12. 移動行

要改變 UITableView 對象所顯示的行的排列位置,需要為數(shù)據(jù)源實現(xiàn)另一個UITableViewDataSource 協(xié)議的方法,
首先要為數(shù)據(jù)源實現(xiàn)移動方法: moveItemAtIndex:toIndex:,為 HQLItemStore 增加該新方法,同樣需要先在 .h 文件中聲明,然后在 .m 文件中實現(xiàn)。

- (void)moveItemAtIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex {
    if (fromIndex == toIndex) {
        return;
    }

    //得到要移動的對象的指針,以便稍后能將其插入新的位置
    Item *item = self.privateItems [fromIndex];

    //將item從allItem數(shù)組所在位置中移除
    [self.privateItems removeObjectAtIndex:fromIndex];

    //根據(jù)新的索引的位置,將item重新插回allItem數(shù)組新的位置
    [self.privateItems insertObject:item atIndex:toIndex];
}

接下來在 HQLItemViewController.m 中實現(xiàn)tableView:moveRowAtIndexPath:toIndexPath:,更新 HQLItemStore 對象。

- (void)tableView:(UITableView *)tableView
moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath
  toIndexPath:(NSIndexPath *)destinationIndexPath {

    [[HQLItemStore sharedStore]moveItemAtIndex:sourceIndexPath.row toIndex:destinationIndexPath.row];
}

(二)在代碼中使用自動布局

將之前添加到 Interface Builder 中的 ImageView 刪除,改用代碼方式創(chuàng)建,并使用視覺格式化語言 VFL 為其自動布局:

  • 通常,如果是創(chuàng)建整個視圖層次結(jié)構(gòu)及所有視圖約束,就覆蓋 loadView 方法;
  • 如果只是向通過 NIB 文件創(chuàng)建的視圖層次結(jié)構(gòu)中添加一個視圖或約束,就覆蓋 viewDidLoad 方法。
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // ------------------------
    // 在代碼中使用自動布局 VFL 視覺化格式語言
    // 創(chuàng)建 UIImageView 對象
    UIImageView *iv = [[UIImageView alloc] initWithImage:nil];
    // 設(shè)置 UIImageView 對象的內(nèi)容縮放模式
    iv.contentMode = UIViewContentModeScaleAspectFit;
    
    // 在 Apple 引入自動布局系統(tǒng)之前,iOS 一直使用自動縮放掩碼(autoresizing masks)縮放視圖,以適配不同大小的屏幕。
    // 默認(rèn)情況下,視圖會將自動縮放掩碼轉(zhuǎn)換為對應(yīng)的約束,這類約束經(jīng)常會與手動添加的約束產(chǎn)生沖突。
    // 告訴自動布局系統(tǒng)不要將自動縮放掩碼轉(zhuǎn)換為約束
    iv.translatesAutoresizingMaskIntoConstraints = NO;
    // 將 UIImageView 對象添加到 view 上
    [self.view addSubview:iv];
    // 將 UIImageView 對象賦給 imageView 屬性
    self.imageView = iv;
    
    
    // 初始 UITextField 的內(nèi)容放大優(yōu)先級是 250,而 imageView 的內(nèi)容放大優(yōu)先級是 251
    // 如果用戶選擇了一張小尺寸圖片,自動布局系統(tǒng)會增加 UITextField 對象的高度,使得高度超出 UITextField 對象的固有內(nèi)容大小
    // 將 imageView 垂直方向的優(yōu)先級設(shè)置為比其他視圖低的數(shù)值
    // 設(shè)置垂直方向上的【內(nèi)容放大優(yōu)先級】
    [self.imageView setContentHuggingPriority:200
                                      forAxis:UILayoutConstraintAxisVertical];
    // 設(shè)置垂直方向上的【內(nèi)容縮小優(yōu)先級】
    [self.imageView setContentCompressionResistancePriority:700
                                                    forAxis:UILayoutConstraintAxisVertical];
    // 創(chuàng)建視圖名稱字典,將名稱與視圖對象關(guān)聯(lián)起來
    NSDictionary *nameMap = @{
                              @"imageView" :self.imageView,
                              @"dateLabel" :self.dateLabel,
                              @"toolbar"   :self.toolbar
                              };
    // imageView 的左邊和右邊與父視圖的距離都是0點
    NSArray *horizontalConstraints =
        [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[imageView]-0-|"
                                                options:0
                                                metrics:nil
                                                  views:nameMap];
    // imageView 的頂邊與 dateLabel 的距離是8點,底邊與 toolbar 的距離也是8點
    NSArray *verticalConstrants = [NSLayoutConstraint
            constraintsWithVisualFormat:@"V:[dateLabel]-[imageView]-[toolbar]"
                                options:0
                                metrics:nil
                                  views:nameMap];
    
    // 將兩個 NSLayoutConstraint 對象數(shù)組添加到 HQLDetailViewControl 的 view 中
    [self.view addConstraints:horizontalConstraints];
    [self.view addConstraints:verticalConstrants];
    
}

視覺化格式語言(visual formart language,VFL)

  • 視覺化格式語言 定義了一系列使用字符串描述約束的象形語法,而這類字符串稱為視覺化格式字符串
  • 視覺化格式字符串可以描述一個方向上的多個約束。
字符 含義
H 水平方向(horizontal)
V 垂直方向 (vertical)
[] 視圖需要寫在方括號[ ]中
| 表示父視圖
-10- 約束距離為10
[someView] (==50) 限定某個視圖的寬或者高為50
  • 描述水平間距的視覺化格式字符串:
@"H:|-0-[imageView]-0-|"

含義:imageView 的左邊和右邊與父視圖的距離都是0點。

在視覺化格式語言中,0及其連接符可以省略不寫,即

@"H:|[imageView]|"

更復(fù)雜的約束:

@"H:|-20-[imageView1]-10-[imageView2]-20-|"
  • 垂直方向上:
  • 在垂直方向上,字符串的左邊表示頂邊,右邊表示底邊。
@"V:[dateLabel]-[imageView]-[toolbar]"

含義:mageView 的頂邊與 dateLabel 的距離是8點,底邊與 toolbar 的距離也是8點。

@"V:[someView (==50)]"

含義:限定某個視圖的寬或者高為50

為了讓自動布局系統(tǒng)知道視覺格式化字符串中的名稱所表示的視圖對象,需要通過視圖名稱字典將名稱與視圖對象關(guān)聯(lián)起來。

// 創(chuàng)建視圖名稱字典,將名稱與視圖對象關(guān)聯(lián)起來
NSDictionary *nameMap = @{
                          @"imageView" :self.imageView,
                          @"dateLabel" :self.dateLabel,
                          @"toolbar"   :self.toolbar
                         };

如何判斷約束應(yīng)該添加到哪個視圖中?

  • 如果約束同時對【多個父視圖相同的視圖】起作用,那么約束應(yīng)該添加到它們的父視圖中。
  • 如果約束只對【某個視圖自身】起作用,那么約束應(yīng)該添加到該視圖中。
  • 如果約束同時對【多個父視圖不同的視圖】起作用,但是這些視圖在層次結(jié)構(gòu)中有共同的祖先視圖,那么約束應(yīng)該添加到它們最近一級的祖先視圖中。
  • 如果約束同時對【某個視圖及其父視圖】起作用,那么約束應(yīng)該添加到它們的父視圖中。
// 將兩個 NSLayoutConstraint 對象數(shù)組添加到 HQLDetailViewControl 的 view 中
[self.view addConstraints:horizontalConstraints];
[self.view addConstraints:verticalConstrants];

NSLayoutConstraint

// view1.attr1 relation view2.attr2 * multiplier + c 
+ (instancetype)constraintWithItem:(id)view1
                         attribute:(NSLayoutAttribute)attr1
                         relatedBy:(NSLayoutRelation)relation
                            toItem:(id)view2
                         attribute:(NSLayoutAttribute)attr2
                        multiplier:(CGFloat)multiplier
                          constant:(CGFloat)c;

(三)自動轉(zhuǎn)屏,UIPopoverController 與模態(tài)視圖控制器

自動轉(zhuǎn)屏

設(shè)備類型

物理設(shè)備類型

typedef enum UIUserInterfaceIdiom : NSInteger {
    UIUserInterfaceIdiomUnspecified = -1,
    UIUserInterfaceIdiomPhone,
    UIUserInterfaceIdiomPad,
    UIUserInterfaceIdiomTV,
    UIUserInterfaceIdiomCarPlay
} UIUserInterfaceIdiom;

設(shè)備方向(device orientation)

設(shè)備方向指的是設(shè)備的物理方向

typedef enum UIDeviceOrientation : NSInteger {
    UIDeviceOrientationUnknown,             // 未知方向
    UIDeviceOrientationPortrait,            // 正的豎排方向
    UIDeviceOrientationPortraitUpsideDown,  // 倒置方向
    UIDeviceOrientationLandscapeLeft,       // 左旋轉(zhuǎn)方向
    UIDeviceOrientationLandscapeRight,      // 右旋轉(zhuǎn)方向
    UIDeviceOrientationFaceUp,              // 正面朝上
    UIDeviceOrientationFaceDown             // 正面朝下
} UIDeviceOrientation;

界面方向(interface orientation)

界面方向指的是用戶所看到的應(yīng)用界面的方向。

typedef enum UIInterfaceOrientation : NSInteger {
    // 未知方向
    UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
    // 豎排方向,主屏幕按鈕位于屏幕下方
    UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
    // 豎排方向,主屏幕按鈕位于屏幕上方
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    // 橫排方向,主屏幕按鈕位于屏幕右側(cè)
    UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
    // 橫排方向,主屏幕按鈕位于屏幕左側(cè)
    UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} UIInterfaceOrientation;

自動轉(zhuǎn)屏通告機(jī)制

// 視圖即將顯示時調(diào)用
- (void)viewWillAppear:(BOOL)animated {

    // 添加自動轉(zhuǎn)屏通知:iPhone 橫屏狀態(tài)下禁用拍照按鈕
    [[NSNotificationCenter defaultCenter]
        addObserver:self
           selector:@selector(deviceOrientationDidChange:)
               name:UIDeviceOrientationDidChangeNotification
             object:nil];
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    
}

// 視圖即將出棧時調(diào)用
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
  
    [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter]
        removeObserver:self
                  name:UIDeviceOrientationDidChangeNotification
                object:nil];
}

- (void)deviceOrientationDidChange:(NSNotification *)notification {
    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    [self prepareViewsForOrientation:orientation];
}

- (void)prepareViewsForOrientation:(UIInterfaceOrientation)orientation {
    // 如果是 iPad,則不執(zhí)行任何操作
    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
        return;
    }
    
    // 判斷設(shè)備是否處于橫屏方向
    if (UIInterfaceOrientationIsLandscape(orientation)) {
        self.imageView.hidden = YES;
        self.cameraButton.enabled = NO;
    } else {
        self.imageView.hidden = NO;
        self.cameraButton.enabled = YES;
    }
}

UIPopoverController [Deprecated]

類型為 Block 的 completion 實參

之前添加的新項目是直接插入列表中顯示

// 添加新項目
- (IBAction)addNewItem:(id)sender {
    // 創(chuàng)建新的 Item 對象并將其加入 HQLItemStore 對象
    Item *newItem = [[HQLItemStore sharedStore] createItem];    
    // 獲取新創(chuàng)建的對象在 allItem 數(shù)組中的索引
    NSInteger lastRow = [[[HQLItemStore sharedStore] allItems] indexOfObject:newItem];
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:lastRow
                                                inSection:0];
    // 將新行插入UITableview對象
    [self.tableView insertRowsAtIndexPaths:@[indexPath]
                          withRowAnimation:UITableViewRowAnimationTop];
}

現(xiàn)在點擊 ”+” 按鈕添加新項目 ? 把新項目以模態(tài)視圖的方式顯示在 HQLDetailViewControl 對象中;

如果選擇 Cancel 取消,則刪除剛剛創(chuàng)建的新項目。

如果選擇 Done 完成,則添加新項目到列表中,返回的時候還要刷新列表。

實現(xiàn)步驟:

1. HQLDetailViewControl.h 中添加一個 Block 屬性。

@property (nonatomic, copy) void(^dismissBlock)(void);

2. 添加新項目時,把新創(chuàng)建的 HQLDetailViewControl 對象以新創(chuàng)建的 UINavigationController 的根視圖控制器模態(tài)呈現(xiàn)

// 添加新項目
- (IBAction)addNewItem:(id)sender {
    // 創(chuàng)建新的 Item 對象并將其加入 HQLItemStore 對象
    Item *newItem = [[HQLItemStore sharedStore] createItem];
    
    // 把新項目以模態(tài)視圖的方式顯示在 HQLDetailViewControl 對象中
    HQLDetailViewControl *detailViewController = [[HQLDetailViewControl alloc] initForNewItem:YES];
    detailViewController.item = newItem;
    // ?? Block 的代碼塊
    detailViewController.dismissBlock = ^{
        [self.tableView reloadData];
    };
  
    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
    // 修改視圖控制器的模態(tài)樣式(對于 iPad 有效):頁單樣式
    navController.modalPresentationStyle = UIModalPresentationFormSheet;
    [self presentViewController:navController animated:YES completion:nil];
}

3. 修改指定初始化方法

- (instancetype)initForNewItem:(BOOL)isNew {
    self = [super initWithNibName:nil bundle:nil];
    if (self) {
        if (isNew) {
            // 導(dǎo)航欄完成按鈕
            UIBarButtonItem *doneItem = [[UIBarButtonItem alloc]
                            initWithBarButtonSystemItem:UIBarButtonSystemItemDone
                                                 target:self
                                                 action:@selector(save:)];
            self.navigationItem.rightBarButtonItem = doneItem;
            // 導(dǎo)航欄取消按鈕
            UIBarButtonItem *cancelItem = [[UIBarButtonItem alloc]
                            initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
                                                 target:self
                                                 action:@selector(cancel:)];
            self.navigationItem.leftBarButtonItem = cancelItem;
        }
    }
    return self;
}

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    @throw [NSException exceptionWithName:@"Wrong initializer" reason:@"Use initForNewItem" userInfo:nil];
    return nil;
}

4. 實現(xiàn)導(dǎo)航欄按鈕,模態(tài)退出時傳入 Block 對象

- (void)save:(id)sender {
    // UIViewController 對象的 presentingViewController 屬性:
    // 當(dāng)【某個 UIViewController 對象】以模態(tài)形式顯示時,該屬性會指向【~~顯示該對象的那個 UIViewController 對象~~】(包含該對象的 UINavigationController 對象)
    // 所以下面一行代碼的意思是 向 HQLItemsViewController 對象發(fā)送關(guān)閉模態(tài)視圖消息
    [self.presentingViewController dismissViewControllerAnimated:YES
                                                      completion:self.dismissBlock];
}

- (void)cancel:(id)sender {
    // 如果用戶按下了 Cancel 按鈕,就從 HQLItemStore 對象移除新創(chuàng)建的 Item 對象
    [[HQLItemStore sharedStore] removeItem:self.item];
    [self.presentingViewController dismissViewControllerAnimated:YES
                                                      completion:self.dismissBlock];
}

以模態(tài)形式顯示視圖控制器時的動畫效果

typedef enum UIModalTransitionStyle : NSInteger {
    UIModalTransitionStyleCoverVertical = 0,    // 默認(rèn),從底部滑入
    UIModalTransitionStyleFlipHorizontal,       // 以3D 效果翻轉(zhuǎn)
    UIModalTransitionStyleCrossDissolve,        // 淡入
    UIModalTransitionStylePartialCurl           // 模擬書頁卷角
} UIModalTransitionStyle;

示例:

detailViewController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;

線程安全的單例

在同一時間,單線程應(yīng)用只能使用 CPU 的一個核,也只能執(zhí)行一個函數(shù)。相反,多線程應(yīng)用可以同時在不同的 CPU 核上執(zhí)行多個函數(shù)。

單線程應(yīng)用中創(chuàng)建單例

HQLImageStore 類為例:

#pragma 單例類
+ (instancetype)sharedStore{
    static HQLImageStore *sharedStore;
    if (!sharedStore) {
        sharedStore = [[self alloc] initPrivate];
    }
    return sharedStore;
}

// 私有化方法
- (instancetype) initPrivate{
    self = [super init];
    if (self) {
        _dictionary = [[NSMutableDictionary alloc] init];
    }
    return self;
}

// 不允許直接調(diào)用init方法
- (instancetype) init{
    @throw [NSException exceptionWithName:@"Singleton"
                                   reason:@"Use + [HqlImageStore sharedStored]"
                                 userInfo:nil];
    return nil;
}

以上代碼在單線程應(yīng)用中可以正確創(chuàng)建單例,但是在多線程應(yīng)用中,以上代碼可能會創(chuàng)建多個 HQLImageStore 對象。同時,某個線程還可能會訪問其他線程中沒有正確初始化的 HQLImageStore 對象。

使用 dispatch_once ()

+ (instancetype)sharedInstance
{
   static id sharedInstance = nil;
   static dispatch_once_t onceToken = 0;
   dispatch_once(&onceToken, ^{
      sharedInstance = [[self alloc] init];
   });
   return sharedInstance;
}

有可用的代碼塊:??

視圖控制器之間的關(guān)系

  • ① 父—子關(guān)系;
  • ② 顯示—被顯示關(guān)系;

① 父—子關(guān)系

  • 當(dāng)使用 視圖控制器容器(view controller container)時,就會產(chǎn)生擁有父—子關(guān)系的視圖控制器。
  • UINavigationControllerUITabBarControllerUISplitViewController 都是視圖控制器容器
  • 容器對象會將 viewControllers 中的視圖作為子視圖加入自己的視圖。
  • 容器對象通常都有自己的默認(rèn)外觀。
  • 處在同一個父—子關(guān)系下的視圖控制器形成一個族系(family)。

對象相互訪問:

  • 任何容器對象都可以通過 viewControllers 訪問其子對象。
  • 子對象可以通過 UIViewController 對象的四個特定屬性來訪問其容器對象:
    • navinavigationController
    • tabBarController
    • splitViewController
    • parentViewController,該屬性會指向族系中”最近”的那個容器對象。

② 顯示—被顯示關(guān)系;

  • 當(dāng)某個視圖控制器以模態(tài)形式顯示另一個視圖控制器時,就會產(chǎn)生擁有顯示—被顯示關(guān)系的視圖控制器。

  • 在顯示—被顯示關(guān)系中,位于關(guān)系兩頭的視圖控制器不會處于同一個族系中。被顯示的視圖控制器會有自己的族系。

  • 當(dāng)應(yīng)用以模態(tài)形式顯示某個視圖控制器時,負(fù)責(zé)顯示該視圖控制器的將是相關(guān)族系中的頂部視圖控制器

  • 如果設(shè)置 definesPresentationContext 屬性為 YES ,那么該視圖控制器就會自己負(fù)責(zé)顯示新的視圖控制器,而不是將“顯示權(quán)”向上傳遞。

對象相互訪問

視圖控制器 A——>模態(tài)形式顯示視圖控制器 B:

  • A.presentedViewController = B
  • B.presentingViewController = A

保存、讀取與應(yīng)用狀態(tài)

略.

Auto Layout 中的兩個屬性

  • Content Hugging Priority: 內(nèi)容放大優(yōu)先級
  • Content Compression Resistance Priority:內(nèi)容縮小優(yōu)先級

所有視圖都具有 intrinsicContentSize 屬性,表示視圖的固有內(nèi)容大小,自動布局系統(tǒng)會根據(jù)固有內(nèi)容大小自動為視圖添加寬度和高度約束。

如果需要讓自動布局系統(tǒng)在必要時基于固有內(nèi)容大小放大視圖尺寸,則可以為視圖添加一個優(yōu)先級比視圖的內(nèi)容放大優(yōu)先級(Content Hugging Priority)高的約束;

相反,如果需要讓自動布局系統(tǒng)在必要時基于固有內(nèi)容大小縮小視圖尺寸,則可以為視圖添加一個優(yōu)先級比視圖的內(nèi)容縮小優(yōu)先級(Content Compression Resistance Priority)高的約束;

高優(yōu)先級的的視圖會保持固有內(nèi)容大小,低優(yōu)先級的視圖會根據(jù)當(dāng)前約束拉伸或縮小該視圖的高度或?qū)挾取?/p>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內(nèi)容

  • 翻譯自“Collection View Programming Guide for iOS” 0 關(guān)于iOS集合視...
    lakerszhy閱讀 3,901評論 1 22
  • 概述在iOS開發(fā)中UITableView可以說是使用最廣泛的控件,我們平時使用的軟件中到處都可以看到它的影子,類似...
    liudhkk閱讀 9,081評論 3 38
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,195評論 30 471
  • *7月8日上午 N:Block :跟一個函數(shù)塊差不多,會對里面所有的內(nèi)容的引用計數(shù)+1,想要解決就用__block...
    炙冰閱讀 2,512評論 1 14
  • 我向來是讀書是只看書的內(nèi)容,因此對作者并沒有偏愛,但若非說最喜愛的作家,那么張愛玲和簡媜便是我最喜愛的中國作家。張...
    讀書少女金閱讀 1,826評論 2 4