my contentView.frame.size
и layoutMargins, например, layoutMargins.top
меняются после быстрой прокрутки в UITableView вниз / вверх, поэтому вызывается метод scrollViewDidEndDecelerating
.
Однако, когда я устанавливаю scrollView.bounces = false
в моем UITableViewController, фрейм не изменяется:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollView.bounces = false
}
Так как я делаю некоторые вычисления для моего макета TableViewCell, которые зависят от contentView.frame, изменение размера является проблемой для меня. Тем не менее, я не хочу устанавливать scrollView.bounces = false
из-за UX.
Любые идеи о том, как справиться с этим?
Вот мои соответствующие классы:
BasePostsUIViewController
import AVKit
import UIKit
class BasePostsUIViewController<View: PostViewProtocol, TableViewCell: BasePostUITableViewCell>: UIViewController, UITableViewDataSource, UITableViewDelegate {
var viewModel: PostsModelProtocol?
var basePostsView: PostViewProtocol! { return view as? View }
let tableCellIdentifier = "landingPostCell"
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
func setupPostTableView() {
basePostsView.postsTableView.delegate = self
basePostsView.postsTableView.dataSource = self
basePostsView.postsTableView.register(TableViewCell.self, forCellReuseIdentifier: tableCellIdentifier)
}
func displayPosts(viewModel: PostsModelProtocol) {
refreshPosts(viewModel: viewModel)
}
private func refreshPosts(viewModel: PostsModelProtocol) {
self.viewModel = viewModel
basePostsView.postsTableView.reloadData()
}
// MARK: Tableview functions
func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
if viewModel != nil {
return viewModel!.posts.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let post = viewModel!.posts[indexPath.row]
guard let cell = (tableView.dequeueReusableCell(withIdentifier: tableCellIdentifier) as? TableViewCell) else {
return UITableViewCell()
}
cell.tableView = basePostsView.postsTableView
cell.tableViewBounds = basePostsView.postsTableView.bounds
cell.post = post
cellCreated(cell: cell)
return cell
}
func cellCreated(cell _: TableViewCell) {}
func tableView(_: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt _: IndexPath) {
guard let videoCell = cell as? TableViewCell else { return }
videoCell.videoPlayerView.playerView.playerLayer.player!.pause()
videoCell.videoPlayerView.playerView.playerLayer.player = nil
}
}
BasePostUITableViewCell
import AVKit
import FontAwesome_swift
import SnapKit
import SwiftDate
import UIKit
class BasePostUITableViewCell: UITableViewCell {
lazy var profilePictureThumbnailImageView = UIUtils.createUIImageView()
lazy var nameLabel = UIUtils.createUILabel()
lazy var messageTextView = UIUtils.createUITextView()
lazy var videoPlayerView = UIUtils.createVideoPlayerView()
lazy var dateLabel = UIUtils.createUILabel()
lazy var numberOfLikesButton = UIUtils.createUIButton()
lazy var numberOfStarsButton = UIUtils.createUILabel()
lazy var numberOfCommentsButton = UIUtils.createUIButton()
let iconsCounterStackView = UIStackView()
var tableView: UITableView?
var tableViewBounds: CGRect?
var post: Post? {
didSet {
didSetPost()
}
}
func didSetPost() {
if let post = post {
profilePictureThumbnailImageView.setupProfilePictureThumbnail(profilePictureThumbnailUrl: post.author.profilePictureThumbnailUrl!)
nameLabel.text = "\(post.author.firstName!) \(post.author.lastName!)"
messageTextView.text = post.message
videoPlayerView.playVideo(forVideoURL: URL(string: post.video.uri!)!)
dateLabel.text = post.createdAt.toRelative(since: DateInRegion(), style: RelativeFormatter.defaultStyle())
if post.hasUserLikedPost {
UIUtils.setupIconButtonWithText(text: String(post.countLikes!))
} else {
UIUtils.setupIconButtonWithText(text: String(post.countLikes!))
}
UIUtils.setupIconLabelWithText(text: String(post.countLikes!))
UIUtils.setupIconButtonWithText(text: String(post.countComments!))
setupViews()
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.backgroundColor = .white
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
iconsCounterStackView.axis = .horizontal
iconsCounterStackView.distribution = .equalCentering
iconsCounterStackView.addArrangedSubview(numberOfLikesButton)
iconsCounterStackView.addArrangedSubview(numberOfStarsButton)
iconsCounterStackView.addArrangedSubview(numberOfCommentsButton)
contentView.addSubview(profilePictureThumbnailImageView)
contentView.addSubview(nameLabel)
contentView.addSubview(messageTextView)
contentView.addSubview(videoPlayerView)
contentView.addSubview(dateLabel)
contentView.addSubview(iconsCounterStackView)
profilePictureThumbnailImageView.snp.makeConstraints { (make) -> Void in
make.top.leading.equalTo(contentView.layoutMarginsGuide)
make.width.height.equalTo(tableViewBounds!.width * 0.15).priorityHigh()
}
nameLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(contentView.layoutMarginsGuide)
make.leading.equalTo(profilePictureThumbnailImageView.snp.trailing).offset(layoutMargins.left).priorityMedium()
}
nameLabel.numberOfLines = 0
dateLabel.snp.makeConstraints { (make) -> Void in
make.leading.equalTo(profilePictureThumbnailImageView.snp.trailing).offset(layoutMargins.left).priorityMedium()
make.bottom.equalTo(profilePictureThumbnailImageView.snp.bottom)
}
dateLabel.numberOfLines = 0
messageTextView.isEditable = false
messageTextView.snp.makeConstraints { (make) -> Void in
make.top.equalTo(profilePictureThumbnailImageView.snp.bottom).offset(0.5 * layoutMargins.bottom).priorityMedium()
make.leading.trailing.equalTo(contentView.layoutMarginsGuide)
make.bottom.equalTo(videoPlayerView.snp.top)
}
iconsCounterStackView.snp.makeConstraints { (make) -> Void in
make.top.equalTo(videoPlayerView.snp.bottom)
make.leading.trailing.equalTo(contentView.layoutMarginsGuide)
make.bottom.equalTo(contentView.layoutMarginsGuide)
}
setNeedsLayout()
layoutIfNeeded()
let profilePictureThumbnailImageViewHeight = profilePictureThumbnailImageView.frame.height
let messageViewHeight = messageTextView.frame.height
let iconsCounterStackViewHeight = iconsCounterStackView.frame.height
let layoutMarginTopHeight = layoutMargins.top
let layoutMarginBottomHeight = layoutMargins.bottom
let calculatedOffset =
profilePictureThumbnailImageViewHeight +
messageViewHeight +
2 * iconsCounterStackViewHeight +
layoutMarginTopHeight +
2 * layoutMarginBottomHeight +
0.5 * layoutMarginBottomHeight +
4.5 * layoutMarginBottomHeight
videoPlayerView.snp.makeConstraints { (make) -> Void in
make.leading.trailing.equalToSuperview()
make.height.equalTo(tableViewBounds!.height - calculatedOffset).priorityMedium()
}
}
}
PostViewProtocol
import UIKit
protocol PostViewProtocol {
var postsTableView: UITableView { get }
}
LandingViewController
import CoreLocation
import FacebookLogin
import GooglePlaces
import UIKit
protocol LandingDisplayLogic: class {
func checkHasAnyFollowees()
func checkLoginResult(viewModel: Landing.DoFacebookLogin.ViewModel)
func displayPosts(viewModel: PostsModelProtocol)
func routeToAddFollowees()
func routeToMain()
func readCurrentUser()
func storeUserId(viewModel: Landing.StoreUserId.ViewModel)
}
class LandingViewController: BasePostsUIViewController<LandingView, LandingPostTableViewCell>, LandingDisplayLogic {
var interactor: LandingBusinessLogic?
var router: (NSObjectProtocol & LandingRoutingLogic & LandingDataPassing)?
// MARK: Object lifecycle
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
// MARK: Setup
private func setup() {
let viewController = self
let interactor = LandingInteractor()
let presenter = LandingPresenter()
let router = LandingRouter()
viewController.interactor = interactor
viewController.router = router
interactor.presenter = presenter
presenter.viewController = viewController
router.viewController = viewController
router.dataStore = interactor
}
// MARK: Routing
func routeToAddFollowees() {
router?.routeToAddFollowees()
}
func routeToMain() {
router?.routeToMain()
}
// MARK: View lifecycle
override func loadView() {
view = LandingView(frame: CGRect(x: 0, y: navigationBarHeight, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
}
override func viewDidLoad() {
super.viewDidLoad()
setupPostTableView()
setupFacebookLoginButton()
readIntroPosts()
}
// MARK: Handling of Intro Posts Objects
func readIntroPosts() {
let request = Landing.ReadIntroPosts.Request()
interactor?.readIntroPosts(request: request)
}
// MARK: - Setup and Handling of Facebook Login
fileprivate func setupFacebookLoginButton() {
guard let landingView = (self.basePostsView as? LandingView) else {
preconditionFailure("Expected basePostView to be of type LandingView")
}
landingView.facebookLoginButton.addTarget(self, action: #selector(facebookLogin), for: .touchUpInside)
}
@objc func facebookLogin() {
let loginManager = LoginManager()
loginManager.logOut()
loginManager.logIn(permissions: [.publicProfile], viewController: self) { loginResult in
switch loginResult {
case let .failed(error):
// TODO: What should happen here?
log.error("Facebook Login failed: $\(error)")
case .cancelled:
// Nothing to do here
print("User cancelled login.")
case let .success(granted: grantedPermissions, declined: declinedPermissions, token: accessToken):
print("Logged in!")
self.doFacebookLogin(with: accessToken.tokenString)
}
}
}
func doFacebookLogin(with userAccessToken: String) {
let request = Landing.DoFacebookLogin.Request(facebookLogin: FacebookLogin(userAccessToken: userAccessToken))
interactor?.doFacebookLogin(request: request)
}
}
LandingView
import Foundation
import UIKit
class LandingView: UIView, PostViewProtocol {
let postsTableView = UITableView()
let facebookLoginButton = FacebookButton()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func setupViews() {
backgroundColor = .white
setupFacebookLoginButton()
setupPostTableView()
}
fileprivate func setupFacebookLoginButton() {
addSubview(facebookLoginButton)
facebookLoginButton.isUserInteractionEnabled = true
facebookLoginButton.snp.makeConstraints { (make) -> Void in
make.bottom.equalTo(layoutMargins)
make.centerX.equalToSuperview()
}
}
fileprivate func setupPostTableView() {
postsTableView.rowHeight = UITableView.automaticDimension
postsTableView.estimatedRowHeight = 500
postsTableView.translatesAutoresizingMaskIntoConstraints = false
addSubview(postsTableView)
postsTableView.snp.makeConstraints { (make) -> Void in
make.top.leading.trailing.equalToSuperview()
make.bottom.equalTo(facebookLoginButton.snp.top).offset(-1 * layoutMargins.bottom)
}
}
}
LandingPostTableViewCell
class LandingPostTableViewCell: BasePostUITableViewCell {}