iOS 練習項目 Landmarks (四):添加 AutoLayout 約束
- iOS 練習項目 Landmarks (四):添加 AutoLayout 約束
- 新增 topLabel
- 圖片視圖圓形裁切+陰影
- 使用 AutoLayout 為詳情頁的組件添加約束
- DetailViewControllerDelegate
- 為 PlaceCell 添加 AutoLayout 約束
iOS 練習項目 Landmarks (四):添加 AutoLayout 約束
新增 topLabel
與參考視頻對比,返回標簽的右邊還要有一個顯示景點名的標題,在詳情頁新增一個居中的 UILabel 來顯示,命名為 topLabel。
圖片視圖圓形裁切+陰影
要使得圖片展示為一個圓形,先設置一個正方形的 pictureView,再設置它的 layer.cornerRadius 為邊長的一半,masksToBounds 是 layer 的屬性,含義是子視圖是否裁剪圖層邊界,設置為 YES 后,imageView 超出半徑的部分就被裁減掉,這樣就剩下一個圓形。
在圓形外設置一個 4px 的白色 border,就完成了邊界的設置。
然后設置邊界外的陰影,通過設置 layer.shadowOffset、layer.shadowRedius、layer.shadowPath、layer.shadowColor、layer.shadowOpacity 等屬性,但是沒有效果,因為 masksToBounds = true 會把設置的陰影裁剪掉。一種添加陰影的方法是在 pictureView 外套一層 shadowView,shadowView 的 frame 大小和 pictureView 相同,在 shadowView 上設置陰影效果,再讓 picture View 作為 shadowView 的子視圖。
CGRect pictureFrame = CGRectMake(0, 0, 250, 250);// 創建并設置 pictureViewself.pictureView = [[UIImageView alloc] initWithFrame:pictureFrame];[self.pictureView setImage:[self.place picture]];self.pictureView.layer.cornerRadius = 125.0;self.pictureView.layer.borderWidth = 4.0;self.pictureView.layer.borderColor = [UIColor whiteColor].CGColor;// self.pictureView.contentMode = UIViewContentModeScaleAspectFit;self.pictureView.translatesAutoresizingMaskIntoConstraints = NO;// 在 pictureView 上直接設置陰影,會因為 masksToBounds = true 而被裁減掉self.pictureView.layer.masksToBounds = YES;// 在 pictureView 外套一層 shadowViewUIView *shadowView = [[UIView alloc] initWithFrame:self.pictureView.frame];shadowView.layer.shadowColor = [UIColor grayColor].CGColor;shadowView.layer.shadowOffset = CGSizeMake(0, 0);shadowView.layer.shadowOpacity = 1;shadowView.layer.shadowRadius = 9.0;shadowView.layer.cornerRadius = 9.0;[shadowView addSubview:self.pictureView];...[self.view addSubview:shadowView];
- clipToBounds 是 view 的屬性,含義是子視圖只展示父視圖邊界內的內容,邊界外會被裁減掉,默認為 NO。
- masksToBounds 是 layer 的屬性,含義是子視圖是否裁剪圖層邊界,默認為 NO。
使用 AutoLayout 為詳情頁的組件添加約束
UIView 有一個屬性:translatesAutoresizingMaskIntoConstraints,字面意思是把 autoresizingMask 轉換為 Constraints,實際意思是把 frame ,bouds,center 方式布局的視圖自動轉化為約束形式,此時該視圖上約束已經足夠,不需要手動去添加別的約束。
- 用代碼創建的所有view , translatesAutoresizingMaskIntoConstraints 默認是 YES
- 用 IB 創建的所有 view ,translatesAutoresizingMaskIntoConstraints 默認是
NO(autoresize 布局為 YES , autolayout 布局為 NO)。
如果我們要給視圖添加自己創建的約束,會和上述約束沖突,所以使用 AutoLayout 前需要將視圖的 translatesAutoresizingMaskIntoConstraints 屬性設置為 NO。
接下來就是添加約束了:
/* 添加約束 */// topLabel 的上邊緣距離 view 的上邊緣有 50px,且居中顯示[NSLayoutConstraint activateConstraints:@[[self.topLabel.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:50],[self.topLabel.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor], // 設置視圖位于 X 軸居中[self.topLabel.heightAnchor constraintEqualToConstant:50] // 設置視圖的高度為 50 點]];// mapView 的上邊緣位于 topLabel 的下邊緣NSLayoutConstraint *mapTopAttachToTopButtom = [NSLayoutConstraint constraintWithItem:self.mapView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLabel attribute:NSLayoutAttributeBottom multiplier:1 constant:0];[self.view addConstraint:mapTopAttachToTopButtom];[NSLayoutConstraint activateConstraints:@[[self.mapView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor], // 設置地圖視圖位于 X 軸居中// [self.mapView.topAnchor constraintEqualToAnchor:self.topLabel.bottomAnchor],// [self.mapView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],// [self.mapView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],[self.mapView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],[self.mapView.heightAnchor constraintEqualToConstant:290] // 設置地圖視圖的高度為 290px]];// pictureView 的中心位于 mapView 的下邊緣NSLayoutConstraint *pictureCenterYAttachToMapButtom = [NSLayoutConstraint constraintWithItem:self.pictureView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.mapView attribute:NSLayoutAttributeBottom multiplier:1 constant:0];[self.view addConstraint:pictureCenterYAttachToMapButtom];// pictureView 的寬高為 250px,且 X 軸居中[NSLayoutConstraint activateConstraints:@[[self.pictureView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor], // 設置圖片視圖位于 X 軸居中// [self.pictureView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], // 設置圖片視圖位于 Y 軸居中[self.pictureView.widthAnchor constraintEqualToConstant:250], // 設置圖片視圖的寬度為 250px[self.pictureView.heightAnchor constraintEqualToConstant:250] // 設置圖片視圖的高度為 250px]];// sightLabel 位于 pictureView 下方 30px 處,距離屏幕左邊界 20px[NSLayoutConstraint activateConstraints:@[[self.sightLabel.topAnchor constraintEqualToAnchor:self.pictureView.bottomAnchor constant:30],[self.sightLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:20],[self.sightLabel.heightAnchor constraintEqualToConstant:80]]];// starButton 位于 sightLabel 右側,距離 4px[NSLayoutConstraint activateConstraints:@[[self.starButton.leadingAnchor constraintEqualToAnchor:self.sightLabel.trailingAnchor constant:4],[self.starButton.topAnchor constraintEqualToAnchor:self.sightLabel.topAnchor],[self.starButton.bottomAnchor constraintEqualToAnchor:self.sightLabel.bottomAnchor],[self.starButton.widthAnchor constraintEqualToConstant:25],[self.starButton.heightAnchor constraintEqualToConstant:25]]];// scenicAreaLabel 位于 sightLabel 下方,距離 15px[NSLayoutConstraint activateConstraints:@[[self.scenicAreaLabel.topAnchor constraintEqualToAnchor:self.sightLabel.bottomAnchor constant:15],[self.scenicAreaLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:20],[self.scenicAreaLabel.heightAnchor constraintEqualToConstant:20]]];// stateLabel 與 scenicAreaLabel 水平,其右邊界距離屏幕右邊界 20px[NSLayoutConstraint activateConstraints:@[[self.stateLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20],[self.stateLabel.topAnchor constraintEqualToAnchor:self.scenicAreaLabel.topAnchor],[self.stateLabel.bottomAnchor constraintEqualToAnchor:self.scenicAreaLabel.bottomAnchor],[self.stateLabel.heightAnchor constraintEqualToConstant:20]]];
DetailViewControllerDelegate
詳情頁的 starButton 也要能修改數據源,所以在 DetailViewController 里聲明一個 DetailViewControllerDelegate 協議。
@protocol DetailViewControllerDelegate <NSObject>@optional- (void)detailViewController:(DetailViewController *)detailViewController goBackWithFavorite:(BOOL)favorite atIndex:(NSInteger)index;@end@interface DetailViewController : UIViewController@property (nonatomic, weak) id <DetailViewControllerDelegate> detailViewControllerDelegate;...@end
里面有一個可選方法,作用是將 favorite 從詳情頁傳回主頁,根據 index 修改數據源對應下標的 Place 對象的 favorite 屬性。
在 ViewController 引入這個協議,在 tableView:didSelectRowAtIndexPath: 方法中,我們創建了 DetailViewController 對象,之后要設置代理,別忘了設置 index 屬性:
// 設置代理,并且遵守 DetailViewControllerDelegate
detailViewController.detailViewControllerDelegate = self;
// 別忘了設置 index 屬性
[detailViewController setIndex:indexPath.row];
并且實現協議的方法:
# pragma mark - DtailViewControllerDelegate Method- (void)detailViewController:(DetailViewController *)detailViewController goBackWithFavorite:(BOOL)favorite atIndex:(NSInteger)index
{// 修改數據源對應的對象[self.places[index] setFavorite:favorite];// TableView 重新加載被修改了的那一行[placeTable reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:0]] withRowAnimation:UITableViewRowAnimationNone];// [self.placeTable reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
}
為詳情頁的starButton的點擊事件添加一個目標-動作對,點擊按鈕,將starButton.selected保存到favorite,再通知代理執行協議方法:
- (void)starButtonClicked:(UIButton *)sender
{sender.selected = !sender.selected;// [place setFavorite:sender.selected];self.favorite = sender.selected;// 首先判斷代理人是否存在并且是否遵守協議并且實現了協議方法if (_detailViewControllerDelegate && [_detailViewControllerDelegate respondsToSelector:@selector(detailViewController:goBackWithFavorite:atIndex:)]){// 如果滿足判斷條件,則讓代理執行協議方法,此處讓代理人執行協議方法,在代理人那個控制器中的協議方法會被執行[_detailViewControllerDelegate detailViewController:self goBackWithFavorite:self.favorite atIndex:self.index];}
}
為 PlaceCell 添加 AutoLayout 約束
之前提到過:在自己實現的PlaceCell中,目前只有imageView的上面空出了10px,其實下面也要空出距離,實現imageView和contentView的上下都有空位的效果。現在通過 AutoLayout 約束,使得 imageView 的上下分別距離 contentView 5px,就可以實現這個效果,再設置 textLabel 和 starButton 位于 contentView 的 Y 軸居中。
// 添加約束[NSLayoutConstraint activateConstraints:@[[self.imageView.widthAnchor constraintEqualToConstant:50],[self.imageView.heightAnchor constraintEqualToConstant:50],[self.imageView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:20],[self.imageView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:5],[self.imageView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-5],[self.textLabel.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor],[self.textLabel.leadingAnchor constraintEqualToAnchor:self.imageView.trailingAnchor constant:10],[self.starButton.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor], // 設置圖片視圖位于 Y 軸居中[self.starButton.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-10],[self.starButton.widthAnchor constraintEqualToConstant:25],[self.starButton.heightAnchor constraintEqualToConstant:25]]];