«Эта статья участвовала в мероприятии Haowen Convocation Order, щелкните, чтобы просмотреть:Заявки на бэк-энд и фронт-энд с двумя треками, призовой фонд в 20 000 юаней ждет вас, чтобы бросить вызов! "
предисловие
Привет всем, сегодня я собираюсь поделиться с вами тем, как использовать Swift для реализации домашней страницы NetEase Cloud Music; после публикации двух последних статей я получил много внимания и лайков от моих друзей, а также получил несколько очень хорошие отзывы Полезные предложения, еще раз спасибо за ваше признание, ваша поддержка и предложения являются самой большой мотивацией для моей технической продукции.
MVVM
Ну и вернемся к теме, мы использовали в проекте паттерн MVVM, в предыдущей статье мы закончили говорить о Model и ViewModel, так что давайте начнем говорить о третьей части, View! Если у вас есть друг, который вошел из этой статьи, вы могли бы также начать с моегопредыдущий постКажется, что такой взгляд на это может обеспечить связность вашего мышления.
View
Вернемся к нашему проекту проекта, готовому построить наше табличное представление.
Во-первых, создайте свойство хранилища HomeViewModel в нашем домашнем контроллере представления DiscoveryViewController и инициализируйте его. В нашем реальном процессе разработки операция запроса данных незаменима.Мы должны сначала предоставить данные ViewModel, а затем перезагрузить TableView при обновлении данных.
// 首页发现 viewModel
fileprivate var homeViewModel = HomeViewModel()
Далее настроим tableViewDataSource:
// Mark UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
if homeViewModel.sections.isEmpty {
return 0
}
return homeViewModel.sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return homeViewModel.sections[section].rowCount
}
Теперь мы можем приступить к созданию пользовательского интерфейса. В соответствии со стилем NetEase Cloud Music нам нужно создать 12 различных типов ячеек, каждая из которых соответствует элементу модели представления.
Чтобы еще больше улучшить качество кода, мы можем определить базовый класс BaseViewCell для этих ячеек, чтобы с помощью этого базового класса мы могли установить некоторые свойства по умолчанию и сократить ненужную работу по кодированию; кроме того, наблюдая, вы найти, большинство разделов будет содержать headView. Что касается реализации headView, то учащиеся, использовавшие UITableView, должны быть с ней знакомы. Этого можно добиться следующими способами:
- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section; // custom view for header. will be adjusted to default or specified header height
Однако в этом проекте я не планирую использовать описанный выше метод для реализации headView. Основная причина в том, что каждый раздел NetEase Cloud Music имеет закругленные углы. Если мы определяем viewForHeaderInSection, то мы реализуем закругленные углы. Когда вам нужно сделать следующую логику:
- Добавьте закругленные углы в верхний левый и верхний правый углы headView.
- Добавьте закругленные углы к нижней левой и нижней правой ногам ячейки в разделе.
как показано на рисунке:
Мы знаем, что очень важно добавлять закругленные углы в вид. Если вы напрямую вызываете методы angleRadius и masksToBounds для установки закругленных углов, будет происходить рендеринг вне экрана. Более того, на нашей домашней странице много видов со скругленными углами, когда главная загрузка страниц Дисплей будет ощущаться очевидным отставанием, такой опыт не очень хорош! И этими двумя способами нельзя указать ориентацию закругленных углов для вида, это верхний левый угол или нижний правый угол?
Выше упоминалось, что установка закругленных углов для вида приведет к рендерингу вне экрана, если вы не будете осторожны, так как же решить эту проблему! Здесь мы можем нарисовать закругленные углы для представления с помощью UIBezierPath, а также указать ориентацию закругленных углов:
func roundCorners(_ rect: CGRect, corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
Учитывая, что если HeadView создается методом viewForHeaderInSection, то нам нужно отрисовывать скругленные углы для двух представлений, а именно headView, созданного TableViewCell, и viewForHeaderInSection. Здесь я думаю о лучшем способе, просто вызовите метод рисования один раз, то есть реализуйте наш headView в нашей tableViewCell, как показано ниже:
Кроме того, поскольку у каждого Section есть headView, мы можем реализовать это представление головы в базовом классе BaseViewCell:
/// UITableViewCell 的基类
class BaseViewCell: UITableViewCell {
var headerView: JJTableViewHeader?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.backgroundColor = UIColor.homeCellColor
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Далее давайте создадим конкретную ячейку. Из-за слишком большого количества кода здесь показана только часть кода:
/// 首页 Bannerl
class ScrollBannerCell: BaseViewCell {
class var identifier: String {
return String(describing: self)
}
var scrollBanner: JJNewsBanner!
var item: HomeViewModelSection? {
didSet {
guard let item = item as? BannerModel else {
return
}
self.setupUI(model: item)
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
/// 初始化
scrollBanner = JJNewsBanner(frame: CGRect.zero)
self.contentView.addSubview(scrollBanner!)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
func setupUI(model: BannerModel) {
self.scrollBanner.frame = model.frame
self.scrollBanner.updateUI(model: model, placeholderImage: UIImage(named: "ad_placeholder"))
}
}
/// 首页-发现 圆形按钮
class CircleMenusCell: BaseViewCell {
class var identifier: String {
return String(describing: self)
}
var homeMenu: HomeMenu!
var item: HomeViewModelSection? {
didSet {
guard let item = item as? MenusModel else {
return
}
self.setupUI(model: item)
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
/// 初始化
homeMenu = HomeMenu(frame: CGRect.zero)
self.contentView.addSubview(homeMenu!)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
func setupUI(model: MenusModel) {
self.homeMenu.frame = model.frame
self.homeMenu.updateUI(data: model.data)
}
}
....
На самом деле стили представления, отображаемые каждой ячейкой, очень разнообразны, поэтому мы должны создавать разные стили пользовательского интерфейса для ячеек, причем каждый стиль соответствует своей собственной модели данных.
Создайте стиль TableViewCell
Эффект карусели изображений
Прежде всего, верхний слой NetEase Cloud Music — это эффект карусели изображений Как построить этот баннер! Здесь нет обходного пути.Конечно, наиболее часто используемый контент используется для отображения артефакта UICollectionView.Прочитав эту статью, вы обнаружите, что все может использовать UICollectionView.
Код для достижения этого эффекта здесь не будет подробно описан, потому что в моей предыдущей статье я написал учебник для достижения этого эффекта, просто посмотрите эту статью:Используйте UICollectionView для достижения эффекта поворота карточки домашней страницы.
Вход в круглое меню
Эффект очень прост в реализации, интересно только то, что текст посередине кнопки «Ежедневная рекомендация песни» будет меняться вместе с датой, как показано на рисунке:
Однако это также просто реализовать, просто поместите метку посередине. Как показано на этом виде сбоку (рисунок заимствован у автораLeo):
Элементом управления, используемым для полной реализации, по-прежнему является UICollectionView. Часть кода выглядит следующим образом:
import UIKit
import Foundation
import SnapKit
import Kingfisher
class HomeMenuCell: UICollectionViewCell {
lazy var menuLayer: UIView = {
let view = UIView()
view.backgroundColor = UIColor.darkModeMenuColor
return view
}()
lazy var menuIcon: UIImageView = {
let mIcon = UIImageView()
mIcon.tintColor = UIColor.dragonBallColor
return mIcon
}()
lazy var menuText: UILabel = {
let mText = UILabel()
mText.textColor = UIColor.darkModeTextColor
mText.textAlignment = .center
mText.font = UIFont.systemFont(ofSize: 12)
return mText
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
override func layoutSubviews() {
super.layoutSubviews()
self.contentView.addSubview(self.menuLayer)
self.menuLayer.addSubview(self.menuIcon)
self.contentView.addSubview(self.menuText)
self.menuLayer.snp.makeConstraints { (make) in
make.centerX.equalToSuperview()
make.width.equalTo(self.frame.size.width * 0.6)
make.height.equalTo(self.frame.size.width * 0.6)
}
self.menuIcon.snp.makeConstraints { (make) in
make.centerX.equalToSuperview()
make.centerY.equalToSuperview()
make.width.equalTo(self.frame.size.width * 0.6)
make.height.equalTo(self.frame.size.width * 0.6)
}
self.menuText.snp.makeConstraints { (make) in
make.centerX.equalToSuperview()
make.bottom.equalToSuperview()
make.height.equalTo(self.frame.size.width * 0.4)
make.width.equalTo(self.frame.size.width)
}
// 设置菜单圆角
self.menuLayer.layer.cornerRadius = self.frame.size.width * 0.6 * 0.5
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupUI(imageUrl: String, title: String) -> Void {
let cache = KingfisherManager.shared.cache
let imgModify = RenderingModeImageModifier(renderingMode: .alwaysTemplate)
let optionsInfo = [KingfisherOptionsInfoItem.imageModifier(imgModify),
KingfisherOptionsInfoItem.targetCache(cache)]
self.menuIcon.kf.setImage(with: URL(string: imageUrl), placeholder: nil, options: optionsInfo, completionHandler: { ( result ) in
})
self.menuText.text = title
}
}
Рекомендуемые плейлисты/музыкальные клипы/радарные плейлисты/видеоколлекции и т. д.
Сначала посмотрите на эффект пользовательского интерфейса:
Поскольку эффекты этих пользовательских интерфейсов схожи, первая идея, которая возникла, заключалась в том, чтобы поместить UICollectionView в ячейку.Его макет также очень прост, и он может быть предоставлен непосредственно системой без необходимости настраивать макет.
CollectionViewCell в контексте приведенного выше изображения также хорошо определен, поэтому я не буду здесь подробно останавливаться, часть кода выглядит следующим образом:
import UIKit
import SnapKit
import Kingfisher
class CardViewCell: UICollectionViewCell {
/// 封面
lazy var albumCover: UIImageView! = {
let cover = UIImageView()
cover.backgroundColor = UIColor.clear
cover.contentMode = .scaleAspectFill
return cover
}()
/// 描述
lazy var albumDesc: UILabel! = {
let descLabel = UILabel()
descLabel.backgroundColor = UIColor.clear
descLabel.font = UIFont.systemFont(ofSize: 12)
descLabel.numberOfLines = 0
return descLabel
}()
/// 阅读量
var views: String?
/// 内边距
let padding: CGFloat = 5
/// 阅读量按钮
lazy var viewsButton: UIButton! = {
let button = UIButton(type: .custom)
button.titleLabel?.font = UIFont.systemFont(ofSize: 10)
button.backgroundColor = UIColor(red: 182/255, green: 182/255, blue: 182/255, alpha: 0.6)
button.setImage(UIImage(named: "Views"), for: .normal)
button.setTitleColor(.white, for: .normal)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .clear
self.addSubview(self.albumCover)
self.albumCover.addSubview(self.viewsButton)
self.addSubview(self.albumDesc)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
let height: CGFloat = self.bounds.height
let width: CGFloat = self.bounds.width
let descHeight: CGFloat = height * (1/4)
// 封面样式设置
self.albumCover.snp.makeConstraints { (make) in
make.width.equalTo(width)
make.height.equalTo(width)
make.centerX.equalToSuperview()
make.top.equalToSuperview()
}
self.albumCover.roundCorners(self.albumCover.bounds, corners: [.allCorners], radius: 10)
// 设置按钮样式
let viewsRect = self.getStrBoundRect(str: self.views!, font: self.viewsButton.titleLabel!.font, constrainedSize: CGSize.zero)
let viewsW = viewsRect.width
let viewsH = viewsRect.height * 1.2
self.viewsButton.frame = CGRect(x: self.albumCover.frame.width - viewsW - padding, y: padding, width: viewsW, height: viewsH)
self.viewsButton.moveImageLeftTextCenterWithTinySpace(imagePadding: 5)
self.viewsButton.roundCorners(self.viewsButton.bounds, corners: [.allCorners], radius: viewsW * 0.2)
self.albumDesc.snp.makeConstraints { (make) in
make.width.equalTo(width - 10)
make.height.equalTo(descHeight)
make.centerX.equalToSuperview()
make.top.equalTo(self.albumCover.snp.bottom).offset(5)
}
}
....
}
/// 通用的卡片滚动视图,该控件适用于横向滚动并且上图下文形式
class CardCollectionView: UIView {
.....
/// 布局
lazy var cardFlowLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = margin
layout.minimumInteritemSpacing = 0
layout.sectionInset = UIEdgeInsets.init(top: -20, left: margin, bottom: 0, right: 0)
layout.scrollDirection = .horizontal
return layout
}()
/// 歌单的视图
lazy var hotAlbumContainer: UICollectionView = {
let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: self.cardFlowLayout)
collectionView.register(CardViewCell.self, forCellWithReuseIdentifier: RecomendAlbumId)
collectionView.isPagingEnabled = true
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = UIColor.clear
collectionView.bounces = false
return collectionView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.hotAlbumContainer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.hotAlbumContainer.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)
// 设置 item size 大小
self.cardFlowLayout.itemSize = CGSize(width: itemA_width * scaleW, height: self.frame.size.height - 3 * margin)
}
deinit {
self.hotAlbumContainer.delegate = nil
self.hotAlbumContainer.dataSource = nil
}
}
// MARK: - UICollectionViewDelegate
extension CardCollectionView: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
}
// MARK: - UICollectionViewDataSource
extension CardCollectionView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.songList == nil {
return 0
}
return self.songList!.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RecomendAlbumId, for: indexPath) as! CardViewCell
let result:Creative = self.songList![indexPath.row]
if result.creativeType == "voiceList" {
cell.updateUI(coverUrl: (result.uiElement?.image!.imageURL)!, desc: (result.uiElement?.mainTitle!.title)!, views: String((result.creativeEXTInfoVO?.playCount)!))
} else {
let element = result.resources?[0]
cell.updateUI(coverUrl: (element?.uiElement.image.imageURL)!, desc: (element?.uiElement.mainTitle.title)!, views: String((element?.resourceEXTInfo?.playCount)!))
}
return cell
}
}
Персональная рекомендация/новая песня и новый диск с цифровым альбомом/
Далее, давайте создадим еще один стиль. Давайте сначала посмотрим на пользовательский интерфейс:
Поскольку стили «личная рекомендация» и «новая песня и новый цифровой альбом на диске» похожи, они также упоминаются вместе. Здесь я по-прежнему предпочитаю размещать UICollectionView в ячейке. Однако, наблюдая, вы обнаружите, что его стиль пользовательского интерфейса на самом деле изысканный, то есть на той же странице его второй элемент также должен быть выставлен, как этого добиться!
Чтобы на одной странице отображались два элемента, мы должны уменьшить ширину itemSize, чтобы после установки UICollectionViewFlowLayout два элемента могли появиться на одной странице.
Мы знаем, что в свойствах UICollectionView есть свойство пейджинга: isPagingEnabled, при значении true смещение каждого скролла равно ширине его собственного фрейма, когда это свойство пейджинга не задано, его значение по умолчанию равно false , поэтому его прокрутка не будет иметь эффекта пейджинга.
Хорошо, эта мысль верна? На самом деле, когда вы потренируетесь, вы обнаружите, что после этой реализации будет очень головная боль, то есть при прокрутке элемента будет окклюзия, что слишком внимательно для пользователя.
Некоторые люди хотят спросить, может ли элемент управления UICollectionView перемещаться по страницам только в соответствии с размером экрана! Ответ, конечно, нет. Мы также можем реализовать прокрутку страницы по-своему. Согласно документации, Apple предоставляет переопределяемую функцию в определении UICollectionViewFlowLayout:
func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint // return a point at which to rest after scrolling - for layouts that want snap-to-point scrolling behavior
Возвращаемое значение этой функции определяет смещение, когда UICollectionView прекращает прокрутку.Вы можете реализовать пользовательскую прокрутку страниц, переписав эту функцию.Логическая идея переписывания этой функции такова:
- Определите координатную точку CGPoint для записи координаты смещения последней прокрутки.
- Определите два значения как прокручиваемое максимальное смещение и минимальное смещение UICollectionView, которое также равно 0
- Каждый раз, когда прокрутка останавливается, будет вызываться вышеописанная функция func targetContentOffset(...) В этой функции есть параметр OfferContentOffset, который записывает целевые координаты смещения прокрутки, через эту координату и записанные координаты последней прокрутки , то можно судить что это левый скролл или правый скролл
- Если абсолютное значение горизонтального вычитания двух координат больше, чем фиксированное значение (например, одна восьмая ширины элемента), можно судить о том, что произошла подкачка, и тогда текущая прокрутка вычисляется по предложенному ContentOffset. координаты смещения и ширина элемента, номер страницы, если он меньше этого фиксированного значения, разбиение на страницы не происходит.
- Наконец, запишите последние координаты смещения, а затем верните смещение, когда UICollectionView перестанет прокручиваться.
Код реализован следующим образом:
class RowStyleLayout: UICollectionViewFlowLayout {
private var lastOffset: CGPoint!
override init() {
super.init()
lastOffset = CGPoint.zero
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// 初始化
override func prepare() {
super.prepare()
self.collectionView?.decelerationRate = .fast
}
// 这个方法的返回值,决定了 CollectionView 停止滚动时的偏移量
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// 分页的 width
let pageSpace = self.stepSpace()
let offsetMax: CGFloat = self.collectionView!.contentSize.width - (pageSpace + self.sectionInset.right + self.minimumLineSpacing)
let offsetMin: CGFloat = 0
// 修改之前记录的位置,如果小于最小的contentsize或者最大的contentsize则重置值
if lastOffset.x < offsetMin {
lastOffset.x = offsetMin
} else if lastOffset.x > offsetMax{
lastOffset.x = offsetMax
}
// 目标位移点距离当前点距离的绝对值
let offsetForCurrentPointX: CGFloat = abs(proposedContentOffset.x - lastOffset.x)
let velocityX = velocity.x
// 判断当前滑动方向,向左 true, 向右 fasle
let direction: Bool = (proposedContentOffset.x - lastOffset.x) > 0
var newProposedContentOffset: CGPoint = CGPoint.zero
if (offsetForCurrentPointX > pageSpace/8.0) && (lastOffset.x >= offsetMin) && (lastOffset.x <= offsetMax) {
// 分页因子,用于计算滑过的cell数量
var pageFactor: NSInteger = 0
if velocityX != 0 {
// 滑动
// 速率越快,cell 滑过的数量越多
pageFactor = abs(NSInteger(velocityX))
} else {
// 拖动
pageFactor = abs(NSInteger(offsetForCurrentPointX / pageSpace))
}
//设置 pageFactor 的上限为2,防止滑动速率过大,导致翻页过多
pageFactor = pageFactor < 1 ? 1: (pageFactor < 3 ? 1: 2)
let pageOffsetX: CGFloat = pageSpace * CGFloat(pageFactor)
newProposedContentOffset = CGPoint(x: lastOffset.x + (direction ? pageOffsetX : -pageOffsetX), y: proposedContentOffset.y)
} else {
// 滚动距离小于翻页步距,则不进行翻页
newProposedContentOffset = CGPoint(x: lastOffset.x, y: lastOffset.y)
}
lastOffset.x = newProposedContentOffset.x
return newProposedContentOffset
}
// 每滑动一页的间距
public func stepSpace() -> CGFloat {
return self.itemSize.width + self.minimumLineSpacing
}
}
В моей предыдущей статье я написал учебник для достижения этого эффекта, проверьте эту статью:Используйте UICollectionView для достижения эффекта скольжения по страницам.
Музыкальный календарь
Пользовательский интерфейс, как показано:
Эффект музыкального календаря не должен поддерживать горизонтальную прокрутку, поэтому здесь вы можете разместить UIView в ячейке.Для студентов, у которых есть небольшая база разработки iOS, реализовать такой пользовательский интерфейс не должно быть сложно.Вы можете используйте Xib или код Реализация Xib должна быть быстрее в реализации, я не буду объяснять это здесь.
подкаст
Наконец, говоря о последнем интерфейсе, давайте посмотрим на эффект:
После создания стольких пользовательских интерфейсов выше я, должно быть, видел этот эффект. Все его хорошо знают. Есть ли более простой способ, чем использование UICollectionView? То же самое, чтобы построить ячейку с контекстом изображения, но подкасту нужно добавить закругленные углы к изображению, а код очень прост в реализации, поэтому я не буду здесь вдаваться в подробности.
поиск
На том, как создать другую ячейку здесь, если вы сомневаетесь, приветствуете сообщение в разделе комментариев или на моем общественном номере для меня.
Далее мы начинаем говорить о последней части главной страницы — поле поиска. На верхнем этаже музыкальной домашней страницы Netease Cloud есть вид, который состоит из трех частей: левых кнопок, полей поиска, правых кнопок. Эту структуру легко представить в виде UinaVigationItem. Правильно, использование UinaVigationItem для достижения такой структуры пользовательского интерфейса является наиболее эффективным.
Поскольку контроллер домашней страницы в нашем проекте унаследован от UITableViewController, мы можем напрямую установить значения leftBarButtonItem, titleView и rightBarButtonItem в его свойстве UINavigationItem:
// 设置搜索视图
func setupSearchController () {
let leftItem = UIBarButtonItem(image: UIImage(named: "menu")?.withRenderingMode(.alwaysOriginal), style: UIBarButtonItem.Style.plain, target: self, action: #selector(menuBtnClicked))
let rightItem = UIBarButtonItem(image: UIImage(named: "microphone")?.withRenderingMode(.alwaysOriginal), style: UIBarButtonItem.Style.plain, target: self, action: #selector(microphoneBtnClicked))
self.navigationItem.leftBarButtonItem = leftItem
self.navigationItem.rightBarButtonItem = rightItem
self.cusSearchBar = JJCustomSearchbar(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
self.cusSearchBar.delegate = self
self.navigationItem.titleView = self.cusSearchBar
}
Настройте UISearchBar, код выглядит следующим образом:
class JJCustomSearchbar: UISearchBar {
override init(frame: CGRect) {
super.init(frame: frame)
self.searchTextField.placeholder = "has not been"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func adjustPosition() {
var frame :CGRect
frame = self.searchTextField.frame
// 获取 placeholder 大小
let r = self.searchTextField.placeholderRect(forBounds: self.searchTextField.bounds)
let offset = UIOffset(horizontal: (frame.size.width - r.width - 40)/2, vertical: 0)
self.setPositionAdjustment(offset, for: .search)
}
}
Когда мы щелкаем поле поиска вверху, страница должна перейти на реальную страницу поиска, поэтому нам нужно реализовать прокси-функцию UISearchBarDelegate:
extension DiscoveryViewController: UISearchBarDelegate {
// 点击跳转
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
self.musicSearchController = MusicSearchViewController()
self.navigationController?.pushViewController(self.musicSearchController, animated: false)
return true
}
}
Создайте страницу поиска после прыжка
Во-первых, нам нужно реализовать представление поиска.Наш контроллер представления MusicSearchViewController наследуется от UITableViewController, поэтому его UINavigationItem имеет свой собственный searchController. Однако, поскольку для панели поиска необходимо настроить некоторые стили, мы можем сначала определить переменную-член UISearchController, инициализировать ее свойства, а затем присвоить значение Код выглядит следующим образом:
self.searchController = UISearchController(searchResultsController: nil)
self.searchController.delegate = self
self.searchController.searchResultsUpdater = self
self.searchController.searchBar.delegate = self
self.searchController.searchBar.placeholder = "Search"
self.searchController.searchBar.autocapitalizationType = .none
self.searchController.dimsBackgroundDuringPresentation = false
self.navigationItem.hidesBackButton = true
self.navigationItem.searchController = self.searchController
self.navigationItem.searchController?.isActive = true
self.navigationItem.hidesSearchBarWhenScrolling = false
definesPresentationContext = true
В этом проекте мы только реализуем простой демонстрационную функцию поиска только потому, что действительно сделать хорошую работу в поисках, нам нужно «сильное» сотрудничество сервера. В этом проекте мы используем только некоторые статические данные для демонстрации:
musics = [
Results(name: "如果爱"),
Results(name: "情书"),
Results(name: "龙卷风"),
Results(name: "半岛铁盒"),
Results(name: "世界末日"),
Results(name: "爱在西元前"),
Results(name: "等你下课"),
Results(name: "黑色幽默"),
Results(name: "我不配")
]
Следующим шагом для источника данных является реализация функции поиска данных, ввод названия песни для поиска в строке поиска и перечисление результатов поиска на странице. Здесь нам нужно реализовать два прокси UISearchResultsUpdating и UISearchBarDelegate, получить входное значение через UISearchBar, затем найти его в предоставленном источнике данных и перезагрузить наше табличное представление:
extension MusicSearchViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
filterContentForSearchText(searchBar.text!)
}
}
extension MusicSearchViewController: UISearchBarDelegate{
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchBar.text!)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
self.navigationController?.popViewController(animated: true)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.searchController.searchBar.resignFirstResponder()
}
}
func filterContentForSearchText(_ searchText: String){
filteredMusic = musics.filter{ music in
return music.name.lowercased().contains(searchText.lowercased()) || searchText == ""
}
tableView.reloadData()
}
конец
На этом использование MVVM для создания домашней страницы NetEase Cloud Music почти завершено. Давайте еще раз подведем итоги. В этой статье мы в основном объясняем, как создавать представления пользовательского интерфейса. Поскольку стили ячеек на нашей домашней странице различны, но похожи, мы создали базовый класс BaseViewCell для отображения одних и тех же мест в ячейках; затем мы создали разные стили пользовательского интерфейса в каждой ячейке, используя артефакт UICollectionView для достижения этих эффектов; наконец, реализована простая функция поиска.
Ну, это и есть обмен~ В следующей статье мы поговорим об адаптации приложения.
Весь код для этой статьи находится в моемGithubДобро пожаловать в звезду ✨.
Прошлые статьи:
- Возьмите вас за руку с домашней страницей NetEase Cloud Music (2)
- Возьмите вас за руку с домашней страницей NetEase Cloud Music (1)
- Код для написания комментариев? напиши и ты проиграешь
- Я так долго не изучал Codable после его выпуска
- iOS изящно обрабатывает сетевые данные, не так ли? Почему бы тебе не посмотреть на это
- Пользовательский макет UICollectionView! Достаточно
Куплю тебе выпить ☕️ превью внимание О ~ +
-
Не забудьте поставить лайк после прочтения, есть 👍 и мотивация
-
Обратите внимание на общественный номер ---HelloWorld Цзе Шао, первый раз подтолкнуть новую позу
Оригинал статьи, написание ограничено, если есть неточности в статье, сообщите пожалуйста.