feature: add SearchRecommendTagsCollectionViewCell
This commit is contained in:
parent
09320bf99c
commit
dff874af76
|
@ -33,6 +33,7 @@
|
|||
2D34D9CB261489930081BFC0 /* SearchViewController+recomendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9CA261489930081BFC0 /* SearchViewController+recomendView.swift */; };
|
||||
2D34D9D126148D9E0081BFC0 /* APIService+Recommend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */; };
|
||||
2D34D9DB261494120081BFC0 /* APIService+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9DA261494120081BFC0 /* APIService+Search.swift */; };
|
||||
2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D34D9E126149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift */; };
|
||||
2D364F7225E66D7500204FDC /* MastodonResendEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D364F7125E66D7500204FDC /* MastodonResendEmailViewController.swift */; };
|
||||
2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */; };
|
||||
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */; };
|
||||
|
@ -99,6 +100,7 @@
|
|||
2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BA625D10E1000694EC8 /* APIService+Favorite.swift */; };
|
||||
2DF75BB925D1474100694EC8 /* ManagedObjectObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BB825D1474100694EC8 /* ManagedObjectObserver.swift */; };
|
||||
2DF75BC725D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BC625D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift */; };
|
||||
2DFF41892614A4DC00F776A4 /* UIView+Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFF41882614A4DC00F776A4 /* UIView+Constraint.swift */; };
|
||||
5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 5D526FE125BE9AC400460CB9 /* MastodonSDK */; };
|
||||
5DF1054125F886D400D6C0D4 /* ViedeoPlaybackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */; };
|
||||
5DF1054725F8870E00D6C0D4 /* VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1054625F8870E00D6C0D4 /* VideoPlayerViewModel.swift */; };
|
||||
|
@ -339,6 +341,7 @@
|
|||
2D34D9CA261489930081BFC0 /* SearchViewController+recomendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchViewController+recomendView.swift"; sourceTree = "<group>"; };
|
||||
2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Recommend.swift"; sourceTree = "<group>"; };
|
||||
2D34D9DA261494120081BFC0 /* APIService+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Search.swift"; sourceTree = "<group>"; };
|
||||
2D34D9E126149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchRecommendTagsCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
2D364F7125E66D7500204FDC /* MastodonResendEmailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewController.swift; sourceTree = "<group>"; };
|
||||
2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModel.swift; sourceTree = "<group>"; };
|
||||
2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentOffsetAdjustableTimelineViewControllerDelegate.swift; sourceTree = "<group>"; };
|
||||
|
@ -402,6 +405,7 @@
|
|||
2DF75BA625D10E1000694EC8 /* APIService+Favorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Favorite.swift"; sourceTree = "<group>"; };
|
||||
2DF75BB825D1474100694EC8 /* ManagedObjectObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectObserver.swift; sourceTree = "<group>"; };
|
||||
2DF75BC625D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectContextObjectsDidChange.swift; sourceTree = "<group>"; };
|
||||
2DFF41882614A4DC00F776A4 /* UIView+Constraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraint.swift"; sourceTree = "<group>"; };
|
||||
2E1F6A67FDF9771D3E064FDC /* Pods-Mastodon.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon_MastodonUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -706,6 +710,14 @@
|
|||
path = Content;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2D34D9E026149C550081BFC0 /* CollectionViewCell */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2D34D9E126149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift */,
|
||||
);
|
||||
path = CollectionViewCell;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2D364F7025E66D5B00204FDC /* ResendEmail */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1352,6 +1364,7 @@
|
|||
5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */,
|
||||
DB47229625F9EFAD00DA7F53 /* NSManagedObjectContext.swift */,
|
||||
2D32EAB925CB9B0500C9ED86 /* UIView.swift */,
|
||||
2DFF41882614A4DC00F776A4 /* UIView+Constraint.swift */,
|
||||
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */,
|
||||
2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */,
|
||||
2D84350425FF858100EECE90 /* UIScrollView.swift */,
|
||||
|
@ -1394,6 +1407,7 @@
|
|||
DB9D6BE825E4F5340051B173 /* SearchViewController.swift */,
|
||||
2D34D9CA261489930081BFC0 /* SearchViewController+recomendView.swift */,
|
||||
2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */,
|
||||
2D34D9E026149C550081BFC0 /* CollectionViewCell */,
|
||||
);
|
||||
path = Search;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1986,6 +2000,7 @@
|
|||
DB98338725C945ED00AD9700 /* Strings.swift in Sources */,
|
||||
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */,
|
||||
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
|
||||
2DFF41892614A4DC00F776A4 /* UIView+Constraint.swift in Sources */,
|
||||
2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */,
|
||||
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */,
|
||||
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */,
|
||||
|
@ -1995,6 +2010,7 @@
|
|||
DB447697260B439000B66B82 /* CustomEmojiPickerHeaderCollectionReusableView.swift in Sources */,
|
||||
DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */,
|
||||
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */,
|
||||
2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */,
|
||||
2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */,
|
||||
2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */,
|
||||
2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,257 @@
|
|||
//
|
||||
// UIView+Constraint.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by sxiaojian on 2021/3/31.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
enum Dimension {
|
||||
case width
|
||||
case height
|
||||
|
||||
var layoutAttribute: NSLayoutConstraint.Attribute {
|
||||
switch self {
|
||||
case .width:
|
||||
return .width
|
||||
case .height:
|
||||
return .height
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
func constrain(toSuperviewEdges: UIEdgeInsets?) {
|
||||
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return}
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
NSLayoutConstraint(item: self,
|
||||
attribute: .leading,
|
||||
relatedBy: .equal,
|
||||
toItem: view,
|
||||
attribute: .leading,
|
||||
multiplier: 1.0,
|
||||
constant: toSuperviewEdges?.left ?? 0.0),
|
||||
NSLayoutConstraint(item: self,
|
||||
attribute: .top,
|
||||
relatedBy: .equal,
|
||||
toItem: view,
|
||||
attribute: .top,
|
||||
multiplier: 1.0,
|
||||
constant: toSuperviewEdges?.top ?? 0.0),
|
||||
NSLayoutConstraint(item: self,
|
||||
attribute: .trailing,
|
||||
relatedBy: .equal,
|
||||
toItem: view,
|
||||
attribute: .trailing,
|
||||
multiplier: 1.0,
|
||||
constant: toSuperviewEdges?.right ?? 0.0),
|
||||
NSLayoutConstraint(item: self,
|
||||
attribute: .bottom,
|
||||
relatedBy: .equal,
|
||||
toItem: view,
|
||||
attribute: .bottom,
|
||||
multiplier: 1.0,
|
||||
constant: toSuperviewEdges?.bottom ?? 0.0)
|
||||
])
|
||||
}
|
||||
|
||||
func constrain(_ constraints: [NSLayoutConstraint?]) {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate(constraints.compactMap { $0 })
|
||||
}
|
||||
|
||||
func constraint(_ attribute: NSLayoutConstraint.Attribute, toView: UIView, constant: CGFloat?) -> NSLayoutConstraint? {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil}
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
return NSLayoutConstraint(item: self, attribute: attribute, relatedBy: .equal, toItem: toView, attribute: attribute, multiplier: 1.0, constant: constant ?? 0.0)
|
||||
}
|
||||
|
||||
func constraint(_ attribute: NSLayoutConstraint.Attribute, toView: UIView) -> NSLayoutConstraint? {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil}
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
return NSLayoutConstraint(item: self, attribute: attribute, relatedBy: .equal, toItem: toView, attribute: attribute, multiplier: 1.0, constant: 0.0)
|
||||
}
|
||||
|
||||
func constraint(_ dimension: Dimension, constant: CGFloat) -> NSLayoutConstraint? {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil }
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
return NSLayoutConstraint(item: self,
|
||||
attribute: dimension.layoutAttribute,
|
||||
relatedBy: .equal,
|
||||
toItem: nil,
|
||||
attribute: .notAnAttribute,
|
||||
multiplier: 1.0,
|
||||
constant: constant)
|
||||
}
|
||||
|
||||
func constraint(toBottom: UIView, constant: CGFloat) -> NSLayoutConstraint? {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil }
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
return NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: toBottom, attribute: .bottom, multiplier: 1.0, constant: constant)
|
||||
}
|
||||
|
||||
func pinToBottom(to: UIView, height: CGFloat) {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
constraint(.width, toView: to),
|
||||
constraint(toBottom: to, constant: 0.0),
|
||||
constraint(.height, constant: height)
|
||||
])
|
||||
}
|
||||
|
||||
func constraint(toTop: UIView, constant: CGFloat) -> NSLayoutConstraint? {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil }
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
return NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: toTop, attribute: .top, multiplier: 1.0, constant: constant)
|
||||
}
|
||||
|
||||
func constraint(toTrailing: UIView, constant: CGFloat) -> NSLayoutConstraint? {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil }
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
return NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: toTrailing, attribute: .trailing, multiplier: 1.0, constant: constant)
|
||||
}
|
||||
|
||||
func constraint(toLeading: UIView, constant: CGFloat) -> NSLayoutConstraint? {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return nil }
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
return NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: toLeading, attribute: .leading, multiplier: 1.0, constant: constant)
|
||||
}
|
||||
|
||||
func constrainTopCorners(sidePadding: CGFloat, topPadding: CGFloat, topLayoutGuide: UILayoutSupport) {
|
||||
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
constraint(.leading, toView: view, constant: sidePadding),
|
||||
NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: topPadding),
|
||||
constraint(.trailing, toView: view, constant: -sidePadding)
|
||||
])
|
||||
}
|
||||
|
||||
func constrainTopCorners(sidePadding: CGFloat, topPadding: CGFloat) {
|
||||
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
constraint(.leading, toView: view, constant: sidePadding),
|
||||
constraint(.top, toView: view, constant: topPadding),
|
||||
constraint(.trailing, toView: view, constant: -sidePadding)
|
||||
])
|
||||
}
|
||||
|
||||
func constrainTopCorners(height: CGFloat) {
|
||||
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
constraint(.leading, toView: view),
|
||||
constraint(.top, toView: view),
|
||||
constraint(.trailing, toView: view),
|
||||
constraint(.height, constant: height)
|
||||
])
|
||||
}
|
||||
|
||||
func constrainBottomCorners(sidePadding: CGFloat, bottomPadding: CGFloat) {
|
||||
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
constraint(.leading, toView: view, constant: sidePadding),
|
||||
constraint(.bottom, toView: view, constant: -bottomPadding),
|
||||
constraint(.trailing, toView: view, constant: -sidePadding)
|
||||
])
|
||||
}
|
||||
|
||||
func constrainBottomCorners(height: CGFloat) {
|
||||
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
constraint(.leading, toView: view),
|
||||
constraint(.bottom, toView: view),
|
||||
constraint(.trailing, toView: view),
|
||||
constraint(.height, constant: height)
|
||||
])
|
||||
}
|
||||
|
||||
func constrainLeadingCorners() {
|
||||
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
constraint(.top, toView: view),
|
||||
constraint(.leading, toView: view),
|
||||
constraint(.bottom, toView: view)
|
||||
])
|
||||
}
|
||||
|
||||
func constrainTrailingCorners() {
|
||||
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
constraint(.top, toView: view),
|
||||
constraint(.trailing, toView: view),
|
||||
constraint(.bottom, toView: view)
|
||||
])
|
||||
}
|
||||
|
||||
func constrainToCenter() {
|
||||
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
constraint(.centerX, toView: view),
|
||||
constraint(.centerY, toView: view)
|
||||
])
|
||||
}
|
||||
|
||||
func pinTo(viewAbove: UIView, padding: CGFloat = 0.0, height: CGFloat? = nil) {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
constraint(.width, toView: viewAbove),
|
||||
constraint(toBottom: viewAbove, constant: padding),
|
||||
self.centerXAnchor.constraint(equalTo: viewAbove.centerXAnchor),
|
||||
height != nil ? constraint(.height, constant: height!) : nil
|
||||
])
|
||||
}
|
||||
|
||||
func pin(toSize: CGSize) {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
widthAnchor.constraint(equalToConstant: toSize.width),
|
||||
heightAnchor.constraint(equalToConstant: toSize.height)])
|
||||
}
|
||||
|
||||
func pinTopLeft(padding: CGFloat) {
|
||||
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
|
||||
topAnchor.constraint(equalTo: view.topAnchor, constant: padding)])
|
||||
}
|
||||
|
||||
func pinTopRight(padding: CGFloat) {
|
||||
guard let view = superview else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
view.trailingAnchor.constraint(equalTo: trailingAnchor, constant: padding),
|
||||
topAnchor.constraint(equalTo: view.topAnchor, constant: padding)])
|
||||
}
|
||||
|
||||
func pinTopLeft(toView: UIView, topPadding: CGFloat) {
|
||||
guard superview != nil else { assert(false, "Superview cannot be nil when adding contraints"); return }
|
||||
constrain([
|
||||
leadingAnchor.constraint(equalTo: toView.leadingAnchor),
|
||||
topAnchor.constraint(equalTo: toView.bottomAnchor, constant: topPadding)])
|
||||
}
|
||||
|
||||
/// Cross-fades between two views by animating their alpha then setting one or the other hidden.
|
||||
/// - parameters:
|
||||
/// - lhs: left view
|
||||
/// - rhs: right view
|
||||
/// - toRight: fade to the right view if true, fade to the left view if false
|
||||
/// - duration: animation duration
|
||||
///
|
||||
static func crossfade(_ lhs: UIView, _ rhs: UIView, toRight: Bool, duration: TimeInterval) {
|
||||
lhs.alpha = toRight ? 1.0 : 0.0
|
||||
rhs.alpha = toRight ? 0.0 : 1.0
|
||||
lhs.isHidden = false
|
||||
rhs.isHidden = false
|
||||
|
||||
UIView.animate(withDuration: duration, animations: {
|
||||
lhs.alpha = toRight ? 0.0 : 1.0
|
||||
rhs.alpha = toRight ? 1.0 : 0.0
|
||||
}, completion: { _ in
|
||||
lhs.isHidden = toRight
|
||||
rhs.isHidden = !toRight
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// SearchRecommendTagsCollectionViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by sxiaojian on 2021/3/31.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class SearchRecommendTagsCollectionViewCell: UICollectionViewCell {
|
||||
let backgroundImageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
return imageView
|
||||
}()
|
||||
|
||||
let hashTagTitleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = .white
|
||||
label.font = .preferredFont(forTextStyle: .caption1)
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
return label
|
||||
}()
|
||||
|
||||
let peopleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = .white
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
return label
|
||||
}()
|
||||
|
||||
let flameIconView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
let image = UIImage(systemName: "flame.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .semibold))!.withRenderingMode(.alwaysTemplate)
|
||||
imageView.image = image
|
||||
imageView.tintColor = .white
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
return imageView
|
||||
}()
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: .zero)
|
||||
configure()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
configure()
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchRecommendTagsCollectionViewCell {
|
||||
private func configure() {
|
||||
contentView.addSubview(backgroundImageView)
|
||||
backgroundImageView.constrain(toSuperviewEdges: nil)
|
||||
|
||||
contentView.addSubview(hashTagTitleLabel)
|
||||
hashTagTitleLabel.pinTopLeft(padding: 16)
|
||||
|
||||
contentView.addSubview(peopleLabel)
|
||||
peopleLabel.constrain([
|
||||
peopleLabel.constraint(toTop: contentView, constant: 46),
|
||||
peopleLabel.constraint(toLeading: contentView, constant: 16)
|
||||
])
|
||||
|
||||
contentView.addSubview(flameIconView)
|
||||
flameIconView.pinTopRight(padding: 16)
|
||||
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// SearchViewController+recomemndView.swift
|
||||
// SearchViewController+recommendView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by sxiaojian on 2021/3/31.
|
||||
|
@ -10,9 +10,14 @@ import UIKit
|
|||
|
||||
|
||||
extension SearchViewController {
|
||||
func setuprecomemndView() {
|
||||
recomemndView.dataSource = self
|
||||
recomemndView.delegate = self
|
||||
func setuprecommendView() {
|
||||
recommendView.register(SearchRecommendTagsCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: SearchRecommendTagsCollectionViewCell.self))
|
||||
recommendView.dataSource = self
|
||||
recommendView.delegate = self
|
||||
}
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
recommendView.collectionViewLayout.invalidateLayout()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,9 +26,20 @@ extension SearchViewController: UICollectionViewDelegate {
|
|||
}
|
||||
|
||||
extension SearchViewController: UICollectionViewDataSource {
|
||||
func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
return (self.viewModel.recommendAccounts.isEmpty ? 0 : 1) + (self.viewModel.recommendHashTags.isEmpty ? 0 : 1)
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
switch section {
|
||||
case 0:
|
||||
return viewModel.recommendHashTags.count
|
||||
case 1:
|
||||
return viewModel.recommendAccounts.count
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
return UICollectionViewCell()
|
||||
|
|
|
@ -27,7 +27,7 @@ final class SearchViewController: UIViewController, NeedsDependency {
|
|||
return searchBar
|
||||
}()
|
||||
|
||||
let recomemndView: UICollectionView = {
|
||||
let recommendView: UICollectionView = {
|
||||
let flowLayout = UICollectionViewFlowLayout()
|
||||
flowLayout.scrollDirection = .horizontal
|
||||
let view = ControlContainableCollectionView(frame: .zero, collectionViewLayout: flowLayout)
|
||||
|
|
|
@ -9,6 +9,7 @@ import Foundation
|
|||
import Combine
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
import OSLog
|
||||
|
||||
final class SearchViewModel {
|
||||
|
||||
|
@ -25,6 +26,39 @@ final class SearchViewModel {
|
|||
|
||||
init(context: AppContext) {
|
||||
self.context = context
|
||||
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
return
|
||||
}
|
||||
context.apiService.recommendTrends(domain: activeMastodonAuthenticationBox.domain, query: Mastodon.API.Trends.Query(limit: 5))
|
||||
.sink { completion in
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: recommendTrends fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||
case .finished:
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: recommendTrends success", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
break
|
||||
}
|
||||
|
||||
} receiveValue: { [weak self] tags in
|
||||
guard let self = self else { return }
|
||||
self.recommendHashTags = tags.value
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
context.apiService.recommendAccount(domain: activeMastodonAuthenticationBox.domain, query: nil, mastodonAuthenticationBox: activeMastodonAuthenticationBox)
|
||||
.sink { completion in
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: recommendAccount fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||
case .finished:
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: recommendAccount success", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
break
|
||||
}
|
||||
} receiveValue: { [weak self] accounts in
|
||||
guard let self = self else { return }
|
||||
self.recommendAccounts = accounts.value
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ extension APIService {
|
|||
|
||||
func recommendAccount(
|
||||
domain: String,
|
||||
query: Mastodon.API.Suggestions.Query,
|
||||
query: Mastodon.API.Suggestions.Query?,
|
||||
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
||||
let authorization = mastodonAuthenticationBox.userAuthorization
|
||||
|
@ -23,7 +23,7 @@ extension APIService {
|
|||
|
||||
func recommendTrends(
|
||||
domain: String,
|
||||
query: Mastodon.API.Trends.Query
|
||||
query: Mastodon.API.Trends.Query?
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> {
|
||||
return Mastodon.API.Trends.get(session: session, domain: domain, query: query)
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ extension Mastodon.API.Suggestions {
|
|||
public static func get(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
query: Mastodon.API.Suggestions.Query,
|
||||
query: Mastodon.API.Suggestions.Query?,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
||||
let request = Mastodon.API.get(
|
||||
|
|
|
@ -31,7 +31,7 @@ extension Mastodon.API.Trends {
|
|||
public static func get(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
query: Mastodon.API.Trends.Query
|
||||
query: Mastodon.API.Trends.Query?
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> {
|
||||
let request = Mastodon.API.get(
|
||||
url: trendsURL(domain: domain),
|
||||
|
|
Loading…
Reference in New Issue