Как построить макет как у GrabRewards
На этой неделе я опубликовал пост «Внедрение гибкого пользовательского интерфейса с помощью UITableView», и меня попросили сделать демонстрацию, например, GrabRewards.
В этом посте рассказывается, как создать GrabRewards с помощью моего knStaticListController
. Проверьте это.
Загрузите стартовый проект на GrabRewards.
Основная ветвь является стартовой. Вы можете оформить заказ в ветке completed
чтобы увидеть результат.
Мы строим макет, как этот экран
Я не строю весь экран, элементы ниже исключены.
- Проведите по верхней вкладке (Обзор, Мои награды)
- Вызов API (только фиктивные данные)
- Нижняя полоса точек (вместо этого сплошной цвет)
Давайте начнем.
я положил UICollectionView
внутри вида, CategoryView
. А также CategoryView
помещается внутрь knTableCell
.
1. Добавить модель
Проанализируйте целевой пользовательский интерфейс, у нас есть коллекция категорий (значок и имя).
struct Category {
var icon: String?
var name: String?
init(icon: String, name: String) {
self.icon = icon
self.name = name
}
}
2. КатегорияЯчейка
Настройка макета и данных для ячейки представления коллекции
let padding: CGFloat = 24
class CategoryCollectionCell: knCollectionCell {
let iconImageView = UIMaker.makeImageView()
let nameLabel = UIMaker.makeLabel(alignment: .center)
// 1
var data: Category? {
didSet {
iconImageView.image = UIImage(named: data?.icon ?? "")
nameLabel.text = data?.name
}
}
override func setupView() {
// 2
let iconWrapper = UIMaker.makeView(background: .lightGray)
iconWrapper.addSubviews(views: iconImageView)
iconImageView.square(edge: 32)
iconImageView.center(toView: iconWrapper)
addSubviews(views: iconWrapper, nameLabel)
iconWrapper.setCorner(radius: 36)
iconWrapper.square()
iconWrapper.horizontal(toView: self, space: padding / 2)
iconWrapper.top(toView: self, space: padding)
nameLabel.horizontal(toView: self, space: padding / 2)
nameLabel.verticalSpacing(toView: iconWrapper, space: padding / 2)
}
}
В функциях инициализации не так много кода, поэтому я поместил их в конец класса. Сосредоточьтесь на макете.
(1)
Я всегда устанавливаю переменную для данных ячейки data
независимо от типов данных.
Я хочу получить что-то:
let something = data.something
вместо
let something = event.something
let something_else = photo.something_else
let other = category.other
ПРИМЕЧАНИЕ
Просто используйте и помните
data
.
(2)
- Настройте макет для значка и метки. Значок находится внутри круга с интервалом, поэтому я добавляю значок в вид и добавляю интервал в этот вид.
32
,36
исправлены для этой демонстрации. Вы можете попробовать несколько разных чисел, чтобы понять. Если вы измените их, вам нужно изменить ширину (96 пикселей) ячейки представления коллекции (следующая часть).padding / 2
: расстояние от обертки значка/nameLabel
к ведущему и замыкающему.
Почему padding / 2
?
- Предположим, что x = заполнение / 2
- Макет такой
|-x-[item_1]-x-| |-x-[item_2]-x-| |-x-[item_3]-x-|
- Расстояние между
item_1
а такжеitem_2
является2x = padding
, равный стандартному интервалу всего макета. => последовательный и более красивый.
3. Вид по категориям
private let viewHeight: CGFloat = 150
class CategoryView: knView {
var datasource = [Category]() { didSet { collectionView.reloadData() }}
var collectionView: UICollectionView!
override func setupView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .white
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(CategoryCollectionCell.self,
forCellWithReuseIdentifier: "CategoryCollectionCell")
collectionView.contentInset = UIEdgeInsets(left: padding / 2, right: padding / 2)
addSubviews(views: collectionView)
collectionView.fill(toView: self)
collectionView.height(viewHeight)
}
}
extension CategoryView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return datasource.count }
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CategoryCollectionCell", for: indexPath) as! CategoryCollectionCell
cell.data = datasource[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 96, height: viewHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0 // remove spacing between 2 cells in UICollectionView
}
}
4. Вьюконтроллер
Добавлять CategoryView
в ячейку и посмотрите, как она отображается в UITableView.
class ViewController: knStaticListController {
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
let categoryView = CategoryView()
override func setupView() {
view.addSubviews(views: tableView)
tableView.fill(toView: view)
let categoryCell = knTableCell.wrap(view: categoryView, space: UIEdgeInsets(bottom: 12))
categoryCell.backgroundColor = .lightGray
datasource = [categoryCell]
categoryView.datasource = [
Category(icon: "1", name: "All"),
Category(icon: "2", name: "Limit Edition"),
Category(icon: "3", name: "Food"),
Category(icon: "4", name: "Grab"),
Category(icon: "5", name: "Service"),
Category(icon: "6", name: "Shopping"),
Category(icon: "7", name: "Entertainment"),
Category(icon: "8", name: "Travel"),
]
}
}
Запустите, и вы увидите первую часть макета GrabRewards.
5. Награда
Подобно категории, Reward
, RewardCollectionCell
, RewardView
настраивает автоматический макет для UICollectionView. Код очень похож. Если некоторые коды недостаточно ясны, дайте мне знать в комментариях.
struct Reward {
var merchantLogoUrl: String?
var merchantName: String?
var bannerUrl: String?
var title: String?
var point = 0
init(merchantLogo: String, merchantName: String, banner: String, title: String, point: Int) {
self.merchantLogoUrl = merchantLogo
self.merchantName = merchantName
self.bannerUrl = banner
self.title = title
self.point = point
}
}
6. RewardCollectionCell
class RewardCollectionCell: knCollectionCell {
let bannerImageView = UIMaker.makeImageView(contentMode: .scaleAspectFill)
let logoImageView = UIMaker.makeImageView()
let merchantNameLabel = UIMaker.makeLabel(font: UIFont.boldSystemFont(ofSize: 15),
color: .white)
let titleLabel = UIMaker.makeLabel(font: UIFont.boldSystemFont(ofSize: 15),
color: .black)
let pointLabel = UIMaker.makeLabel(font: UIFont.systemFont(ofSize: 15),
color: .black)
var data: Reward? {
didSet {
bannerImageView.downloadImage(from: data?.bannerUrl)
logoImageView.downloadImage(from: data?.merchantLogoUrl)
merchantNameLabel.text = data?.merchantName
titleLabel.text = data?.title
let point = data?.point ?? 0
pointLabel.text = "\(point) points"
}
}
override func setupView() {
addSubviews(views: bannerImageView, logoImageView, merchantNameLabel, titleLabel, pointLabel)
bannerImageView.horizontal(toView: self, leftPadding: 0, rightPadding: -padding / 2)
bannerImageView.top(toView: self)
bannerImageView.height(200)
bannerImageView.setCorner(radius: 7)
logoImageView.bottomLeft(toView: bannerImageView, bottom: -padding / 2, left: padding / 2)
logoImageView.square(edge: 32)
logoImageView.setCorner(radius: 7)
merchantNameLabel.leftHorizontalSpacing(toView: logoImageView, space: -padding / 2)
merchantNameLabel.centerY(toView: logoImageView)
titleLabel.horizontal(toView: bannerImageView)
titleLabel.verticalSpacing(toView: bannerImageView, space: padding / 2)
pointLabel.left(toView: titleLabel)
pointLabel.verticalSpacing(toView: titleLabel, space: padding / 2)
pointLabel.bottom(toView: self, space: -padding)
}
}
7. НаградаВью
private let viewHeight: CGFloat = 285
class RewardView: knView {
let titleLabel = UIMaker.makeLabel(font: UIFont.boldSystemFont(ofSize: 20))
var datasource = [Reward]() { didSet { collectionView.reloadData() }}
var collectionView: UICollectionView!
convenience init(title: String) {
self.init()
titleLabel.text = title
}
override func setupView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .white
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(RewardCollectionCell.self,
forCellWithReuseIdentifier: "RewardCollectionCell")
collectionView.contentInset = UIEdgeInsets(left: padding, right: padding / 2)
addSubviews(views: titleLabel, collectionView)
titleLabel.topLeft(toView: self, top: padding, left: padding)
collectionView.horizontal(toView: self)
collectionView.verticalSpacing(toView: titleLabel, space: padding / 2)
collectionView.bottom(toView: self)
collectionView.height(viewHeight)
backgroundColor = .white
}
}
extension RewardView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return datasource.count }
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RewardCollectionCell", for: indexPath) as! RewardCollectionCell
cell.data = datasource[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: screenWidth / 1.25, height: viewHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
8. Обновите ViewController
- Добавьте это ниже
categoryView
let forMeReward = RewardView(title: "Just for you")
let limitRewards = RewardView(title: "Limit rewards")
- Добавьте это ниже
categoryCell
let forMeCell = knTableCell.wrap(view: forMeReward, space: UIEdgeInsets(bottom: 12))
forMeCell.backgroundColor = .lightGray
let limitCell = knTableCell.wrap(view: limitRewards, space: UIEdgeInsets(bottom: 12))
limitCell.backgroundColor = .lightGray
datasource = [categoryCell, forMeCell, limitCell]
- Добавьте это ниже
categoryView.datasource
let rewards = [
Reward(merchantLogo: " merchantName: "Gong Cha", banner: " title: "Save 15%", point: 750),
Reward(merchantLogo: " merchantName: "Gong Cha", banner: " title: "Save 15%", point: 750),
Reward(merchantLogo: " merchantName: "Gong Cha", banner: " title: "Save 15%", point: 750)
]
forMeReward.datasource = rewards
limitRewards.datasource = rewards
Теперь запустите, и вы увидите, что окончательный макет завершен.
С knStaticListView
, вы можете создать большинство пользовательских интерфейсов, которые вы хотите. Поймите это ясно, вы можете создать свой пользовательский интерфейс без особых усилий и с удовольствием.
Дайте мне знать ваше мнение и то, как вы создаете длинный и сложный пользовательский интерфейс.
Загрузите окончательный исходный код на GrabRewardsответвляться completed
.
Наслаждайтесь кодированием.