iOS 练习项目 Landmarks (三):详情页设计

iOS 练习项目 Landmarks (三):详情页设计

为starButton添加目标-动作对直接修改places数组

在获取cell的方法里,给cell.starButton.tag赋值为对应的行号,再添加一个目标-动作对,当点击starButton时,触发btnClicked:方法。

在btnClicked:中,先修改按钮的状态,来切换显示的图片;再根据sender.tag获取要修改的places数组元素的下标,取得对应行号的Place实例,设置其favorite属性与sender.selected一致。

// ViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ...
    // 设置 PlaceCell 对象的 starButton 的 tag 为对应的行号
    cell.starButton.tag = indexPath.row;
    // 添加目标-动作对
    [cell.starButton addTarget:self action:@selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
    ...
    return cell;
}

#pragma mark - starButton click method

- (void)btnClicked:(UIButton *)sender
{
    // NSLog(@"before starButton in row %ld clicked: %d", sender.tag,  [self.places[sender.tag] favorite]);
    // 修改按钮的状态,切换图片
    sender.selected = !sender.selected;
    // 修改数据源(根据当前按钮状态设置对应行号的 Place 对象的 favorite 属性)
    [self.places[sender.tag] setFavorite:sender.selected];
    // NSLog(@"after starButton in row %ld clicked: %d", sender.tag, [self.places[sender.tag] favorite]);
}

PlaceCell通过通知其代理的方式更新数据源

上面的方法有两个缺点:

  1. 定义tag存在风险,可能会和系统的tag相冲突,太多的tag会使得项目变得过于复杂。
  2. 在PlaceCell内部直接修改数据源这种方法不好,界面和数据直接交互不符合MVC原则。

我们可以通过协议让数据和界面解耦。

先定义一个PlaceCellDelegate协议:

// PlaceCellDelegate.h
#import <Foundation/Foundation.h>

@class PlaceCell;

NS_ASSUME_NONNULL_BEGIN

@protocol PlaceCellDelegate <NSObject>

- (void)updateFavorite:(BOOL)newFavorite atPlaceCell:(PlaceCell *)cell;

@end

NS_ASSUME_NONNULL_END

没有PlaceCellDelegate.m,该声明也可以放在PlaceCell里。

从协议的名字就可以看出,它是PlaceCell的协议,需要在PlaceCell.h中引入和声明:

#import <UIKit/UIKit.h>
#import "PlaceCellDelegate.h"

NS_ASSUME_NONNULL_BEGIN

@interface PlaceCell : UITableViewCell

@property (nonatomic) UIButton *starButton;

@property (nonatomic, weak) id<PlaceCellDelegate> placeCellDelegate;
@property (nonatomic) BOOL favorite;

- (void)configureWithFavorite:(BOOL)favor;

@end

NS_ASSUME_NONNULL_END

这里还设置了一个favorite属性,和设置该属性和按钮状态的方法,该属性和对应数据源Place对象的favorite属性保持同步。

还是在获取cell的方法里:

// ViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ...
		// 设置 PlaceCellDelegate
    cell.placeCellDelegate = self;
    [cell configureWithFavorite:[item favorite]];
    ...
    return cell;
}

configureWithFavorite: 方法除了设置PlaceCell的favorite属性,还要设置starButton的状态,进而设置好按钮的图片:

- (void)configureWithFavorite:(BOOL)favor
{
    self.favorite = favor;
    self.starButton.selected = self.favorite;
}

该方法比存方法多做了一件事,就不取名叫setFavorite了。

在ViewController.h中新增PlaceCellDelegate协议:

@interface ViewController : UIViewController
    <UITableViewDelegate, UITableViewDataSource, PlaceCellDelegate>

既然要使用PlaceCellDelegate协议,那就要在ViewController.m里实现PlaceCellDelegate协议的方法:

#pragma mark - PlaceCellDelegate Method

- (void)updateFavorite:(BOOL)newFavorite atPlaceCell:(PlaceCell *)cell
{
    // 去 TableView 查该 cell 对应的 indexPath
    NSIndexPath *indexPath = [placeTable indexPathForCell:cell];
    NSLog(@"before starButton in row %ld clicked: %d", indexPath.row, [self.places[indexPath.row] favorite]);
    // 修改数据源对应的对象
    [self.places[indexPath.row] setFavorite:newFavorite];
    NSLog(@"after starButton in row %ld clicked: %d", indexPath.row, [self.places[indexPath.row] favorite]);
    // TableView 重新加载被修改了的那一行
    [placeTable reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}

PlaceCellDelegate协议的部分就完成了。接下来在PlaceCell中实现收藏按钮点击事件的处理,为starButton添加目标-动作对:

// PlaceCell.m

- (void)initSubView
{
    CGRect buttonFrame = CGRectMake(320, 12, 25, 25);
    // 创建并设置 UIButton 对象
    self.starButton = [UIButton buttonWithType:UIButtonTypeCustom];
    [self.starButton setFrame:buttonFrame];
    ...
    // 添加目标-动作对
    [self.starButton addTarget:self action:@selector(starButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
    
    [self.contentView addSubview:self.starButton];
}

- (void)starButtonClicked:(UIButton *)sender
{
    sender.selected = !sender.selected;
    self.favorite = sender.selected;
    // 通知 placeCellDelegate
    [self.placeCellDelegate updateFavorite:self.favorite atPlaceCell:self];
    // NSLog(@"tag: %ld", sender.tag);
    // PlaceCell *curCell = (PlaceCell *)[sender superview];
    // NSIndexPath *indexPath = [IndexPath indexPathForRow:sender.tag inSection:[tableView reloadRows];
}

当按钮被点击时,首先将sender.selected取反,做到收藏/不收藏的切换,再修改cell的favorite属性,确保和sender.selected一致,再通知其placeCellDelegate(其实就是ViewController),placeCellDelegate就可以根据self.favorite和self(就是PlaceCell对象)去更新数据源。

景点详情页界面设计

参考界面:

在这里插入图片描述

可以看出一共有6个部分:

  1. 地图
  2. 圆形的位于中央的风景照片
  3. 景点名标签
  4. 收藏按钮
  5. 景区名标签
  6. 地区名标签

新建一个DetailViewController,声明这6个部分的组件对象,这些组件对象由一个Place对象来设置,所以还要有一个Place指针。

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>

@class Place;

NS_ASSUME_NONNULL_BEGIN

@interface DetailViewController : UIViewController

@property (nonatomic) MKMapView *mapView;
@property (nonatomic) UIImageView *pictureView;
@property (nonatomic) UILabel *sightLabel;
@property (nonatomic) UIButton *starButton;
@property (nonatomic) UILabel *scenicAreaLabel;
@property (nonatomic) UILabel *stateLabel;

@property (nonatomic) Place *place;

@end

NS_ASSUME_NONNULL_END

在其实现文件中,对于每个组件,先创建其frame,再设置好样式,再根据Place对象的内容进行设置展现的内容,最后把组件添加到view:

// DetailViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    /* width: 393, height: 852 */
    CGFloat viewFrameWidth = self.view.frame.size.width;
    CGFloat viewFrameHeight = self.view.frame.size.height;
    NSLog(@"%lf, %lf", viewFrameWidth, viewFrameHeight);
    CGRect mapFrame = CGRectMake(0, 0, viewFrameWidth, 300);
    CGRect pictureFrame = CGRectMake(viewFrameWidth / 2 - 100, 200, 200, 200);
    CGRect sightFrame = CGRectMake(20, 420, 150, 25);
    CGRect starButtonFrame = CGRectMake(180, 420, 25, 25);
    CGRect scenicAreaFrame = CGRectMake(20, 455, 140, 20);
    CGRect stateFrame = CGRectMake(viewFrameWidth - 120, 455, 100, 20);
    
    // 创建并设置 mapView
    self.mapView = [[MKMapView alloc] initWithFrame:mapFrame];
    [self.mapView setMapType:MKMapTypeStandard];
    MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance([[self place] location].coordinate, 10000, 10000);
    [self.mapView setRegion:viewRegion];
    
    // 创建并设置 pictureView
    self.pictureView = [[UIImageView alloc] initWithFrame:pictureFrame];
    [self.pictureView setImage:[self.place picture]];
    
    // 创建并设置 sightLabel
    self.sightLabel = [[UILabel alloc] initWithFrame:sightFrame];
    [self.sightLabel setFont:[UIFont systemFontOfSize:24]];
    [self.sightLabel setText:[self.place sight]];
    
    // 创建并设置 starButton
    self.starButton = [UIButton buttonWithType:UIButtonTypeCustom];
    [self.starButton setFrame:starButtonFrame];
    [self.starButton setTitle:@"" forState:UIControlStateNormal];
    [self.starButton setImage:[UIImage imageNamed:@"Image_star"] forState:UIControlStateNormal];
    [self.starButton setImage:[UIImage imageNamed:@"Image_starred"] forState:UIControlStateSelected];
    
    // 创建并设置 scenicAreaLabel
    self.scenicAreaLabel = [[UILabel alloc] initWithFrame:scenicAreaFrame];
    [self.scenicAreaLabel setFont:[UIFont systemFontOfSize:16]];
    [self.scenicAreaLabel setText:[self.place scenicArea]];
    
    // 创建并设置 stateLabel
    self.stateLabel = [[UILabel alloc] initWithFrame:stateFrame];
    [self.stateLabel setFont:[UIFont systemFontOfSize:16]];
    [self.stateLabel setText:[self.place state]];
    
    [self.view setBackgroundColor:[UIColor whiteColor]];
    [self.view addSubview:self.mapView];
    [self.view addSubview:self.pictureView];
    [self.view addSubview:self.sightLabel];
    [self.view addSubview:self.starButton];
    [self.view addSubview:self.scenicAreaLabel];
    [self.view addSubview:self.stateLabel];
}

这样一个基本的详情页就创建好了。

点击UITableViewCell跳转到对应详情页

首先要给ViewController提供一个NavigationController,可以直接在Main故事板中添加一个:

在这里插入图片描述

UITableViewDelegate协议里的tableView:didSelectRowAtIndexPath:方法会在选中一个UITableViewCell后触发,我们在这个方法里实现页面跳转:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // NSLog(@"row %ld clicked", indexPath.row);
    DetailViewController *detailViewController = [DetailViewController new];
    [detailViewController setPlace:[self.places objectAtIndex:indexPath.row]];
    [self.navigationController pushViewController:detailViewController animated:NO];
}

步骤分为3步:

  1. 初始化一个DetailViewController对象;
  2. 从数据源取出被点击cell对应行的数据,来设置DetailViewController对象的place属性;
  3. 使用NavigationController的pushViewController:animated:方法进行跳转。

运行效果

详情页如下所示:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值