From 88f010d3d5c0e18a863f64d4771415df896db704 Mon Sep 17 00:00:00 2001 From: Zoia Pribytkova Date: Thu, 28 Feb 2019 16:22:22 +0300 Subject: [PATCH] [iOS] fixed MAPSME-9555 app is crashing when open through review push. --- .../ExpandableTextView.swift | 266 +++++++----------- .../ExpandableTextViewSettings.swift | 32 ++- .../Content/UGC/UGCReviewCell.swift | 18 +- .../Content/UGC/UGCReviewCell.xib | 8 +- .../Content/UGC/UGCYourReviewCell.swift | 18 +- .../Content/UGC/UGCYourReviewCell.xib | 14 +- .../PlacePageLayout/MWMPlacePageLayout.mm | 18 +- .../PlacePage/UGCViewModel/MWMUGCViewModel.mm | 12 + .../UI/Reviews/MWMReviewsViewModelProtocol.h | 2 + .../UI/Reviews/ReviewsViewController.swift | 21 +- 10 files changed, 196 insertions(+), 213 deletions(-) diff --git a/iphone/Maps/Classes/Components/ExpandableTextView/ExpandableTextView.swift b/iphone/Maps/Classes/Components/ExpandableTextView/ExpandableTextView.swift index e21e0d42fc..3967f2fd47 100644 --- a/iphone/Maps/Classes/Components/ExpandableTextView/ExpandableTextView.swift +++ b/iphone/Maps/Classes/Components/ExpandableTextView/ExpandableTextView.swift @@ -1,184 +1,120 @@ import UIKit -@IBDesignable final class ExpandableTextView: UIView { - @IBInspectable var text: String = "" { - didSet { - guard oldValue != text else { return } - update() - } - } - - @IBInspectable var textColor: UIColor? { - get { return settings.textColor } - set { - settings.textColor = newValue ?? settings.textColor - update() - } - } - - @IBInspectable var expandText: String { - get { return settings.expandText } - set { - settings.expandText = newValue - update() - } - } - - @IBInspectable var expandTextColor: UIColor? { - get { return settings.expandTextColor } - set { - settings.expandTextColor = newValue ?? settings.expandTextColor - update() - } - } - - @IBInspectable var numberOfCompactLines: Int { - get { return settings.numberOfCompactLines } - set { - settings.numberOfCompactLines = newValue - update() - } - } - - var textFont: UIFont { - get { return settings.textFont } - set { - settings.textFont = newValue - update() - } - } - - var settings = ExpandableTextViewSettings() { - didSet { update() } - } - - var onUpdate: (() -> Void)? - - override var frame: CGRect { - didSet { - guard frame.size != oldValue.size else { return } - update() - } - } - - override var bounds: CGRect { - didSet { - guard bounds.size != oldValue.size else { return } - update() - } - } - - private var isCompact = true { - didSet { - guard oldValue != isCompact else { return } - update() - } - } - - private func createTextLayer() { - self.layer.sublayers?.forEach { $0.removeFromSuperlayer() } - - var truncate = false - let size: CGSize - let fullSize = text.size(width: frame.width, font: textFont, maxNumberOfLines: 0) - if isCompact { - size = text.size(width: frame.width, font: textFont, maxNumberOfLines: numberOfCompactLines) - truncate = size.height < fullSize.height - if truncate { - let expandSize = expandText.size(width: frame.width, font: textFont, maxNumberOfLines: 1) - let layer = CATextLayer() - layer.position = CGPoint(x: 0, y: size.height) - layer.bounds = CGRect(origin: CGPoint(), size: expandSize) - layer.anchorPoint = CGPoint() - layer.string = expandText - layer.font = CGFont(textFont.fontName as CFString) - layer.fontSize = textFont.pointSize - layer.foregroundColor = expandTextColor?.cgColor - layer.contentsScale = UIScreen.main.scale - - self.layer.addSublayer(layer) - } - } else { - size = fullSize - } - - let layer = CATextLayer() - layer.bounds = CGRect(origin: CGPoint(), size: size) - layer.anchorPoint = CGPoint() - layer.string = text - layer.isWrapped = true - layer.truncationMode = truncate ? kCATruncationEnd : kCATruncationNone - layer.font = CGFont(textFont.fontName as CFString) - layer.fontSize = textFont.pointSize - layer.foregroundColor = textColor?.cgColor - layer.contentsScale = UIScreen.main.scale - - self.layer.addSublayer(layer) - } - - public override func awakeFromNib() { - super.awakeFromNib() - setup() - } - - public convenience init() { - self.init(frame: CGRect()) - } - - public override init(frame: CGRect) { +final class ExpandableReviewView: UIView { + var contentLabel: UILabel = { + let label = UILabel(frame: .zero) + label.numberOfLines = 0 + label.clipsToBounds = true + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + var moreLabel: UILabel = { + let label = UILabel(frame: .zero) + label.clipsToBounds = true + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + var moreZeroHeight: NSLayoutConstraint! + private var settings: ExpandableReviewSettings = ExpandableReviewSettings() + private var isExpanded = false + private var onUpdateHandler: (() -> Void)? + + override init(frame: CGRect) { super.init(frame: frame) - setup() + configureContent() } - - public required init?(coder aDecoder: NSCoder) { + + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - setup() + configureContent() } - - override func mwm_refreshUI() { - super.mwm_refreshUI() - textColor = textColor?.opposite() - expandTextColor = expandTextColor?.opposite() + + override func layoutSubviews() { + super.layoutSubviews() + updateRepresentation() } - - private func setup() { + + func configureContent() { + self.addSubview(contentLabel) + self.addSubview(moreLabel) + let labels: [String: Any] = ["contentLabel": contentLabel, "moreLabel": moreLabel] + var contentConstraints: [NSLayoutConstraint] = [] + let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[contentLabel]-0-[moreLabel]", + metrics: nil, + views: labels) + contentConstraints += verticalConstraints + let moreBottomConstraint = bottomAnchor.constraint(equalTo: moreLabel.bottomAnchor) + moreBottomConstraint.priority = .defaultLow + contentConstraints.append(moreBottomConstraint) + + let contentHorizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[contentLabel]-0-|", + metrics: nil, + views: labels) + contentConstraints += contentHorizontalConstraints + let moreHorizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[moreLabel]-0-|", + metrics: nil, + views: labels) + contentConstraints += moreHorizontalConstraints + NSLayoutConstraint.activate(contentConstraints) + moreZeroHeight = moreLabel.heightAnchor.constraint(equalToConstant: 0.0) + apply(settings: settings) + layer.backgroundColor = UIColor.clear.cgColor isOpaque = true gestureRecognizers = nil addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap))) - update() + } + + func apply(settings: ExpandableReviewSettings) { + self.settings = settings + self.contentLabel.textColor = settings.textColor + self.contentLabel.font = settings.textFont + self.moreLabel.font = settings.textFont + self.moreLabel.textColor = settings.expandTextColor + self.moreLabel.text = settings.expandText } - private var scheduledUpdate: DispatchWorkItem? - - private func updateImpl() { - createTextLayer() - - invalidateIntrinsicContentSize() - onUpdate?() + override func mwm_refreshUI() { + super.mwm_refreshUI() + settings.textColor = settings.textColor.opposite() + settings.expandTextColor = settings.expandTextColor.opposite() } - - func update() { - scheduledUpdate?.cancel() - scheduledUpdate = DispatchWorkItem { [weak self] in self?.updateImpl() } - DispatchQueue.main.async(execute: scheduledUpdate!) - } - - override var intrinsicContentSize: CGSize { - var viewSize = CGSize() - layer.sublayers?.forEach { layer in - viewSize.width = max(viewSize.width, layer.frame.maxX) - viewSize.height = max(viewSize.height, layer.frame.maxY) - } - return viewSize - } - - override func prepareForInterfaceBuilder() { - super.prepareForInterfaceBuilder() - updateImpl() + + func configure(text: String, isExpanded: Bool, onUpdate: @escaping () -> Void) { + contentLabel.text = text + self.isExpanded = isExpanded + contentLabel.numberOfLines = isExpanded ? 0 : settings.numberOfCompactLines + onUpdateHandler = onUpdate } @objc private func onTap() { - isCompact = false + if !isExpanded { + isExpanded = true + updateRepresentation() + contentLabel.numberOfLines = 0 + onUpdateHandler?() + } + } + + func updateRepresentation() { + guard let text = contentLabel.text else { + return + } + if isExpanded { + moreZeroHeight.isActive = true + } else { + let height = (text as NSString).boundingRect(with: CGSize(width: contentLabel.bounds.width, + height: .greatestFiniteMagnitude), + options: .usesLineFragmentOrigin, + attributes: [.font: contentLabel.font], + context: nil).height + if height > contentLabel.bounds.height { + moreZeroHeight.isActive = false + } else { + moreZeroHeight.isActive = true + } + } } } diff --git a/iphone/Maps/Classes/Components/ExpandableTextView/ExpandableTextViewSettings.swift b/iphone/Maps/Classes/Components/ExpandableTextView/ExpandableTextViewSettings.swift index 6494938794..691ec61c6e 100644 --- a/iphone/Maps/Classes/Components/ExpandableTextView/ExpandableTextViewSettings.swift +++ b/iphone/Maps/Classes/Components/ExpandableTextView/ExpandableTextViewSettings.swift @@ -1,19 +1,21 @@ import UIKit -struct ExpandableTextViewSettings { - enum Default { - static let expandText = "…more" - static let expandTextColor = UIColor.blue - static let numberOfCompactLines = 2 - static let textColor = UIColor.darkText - static let textFont = UIFont.preferredFont(forTextStyle: .body) +struct ExpandableReviewSettings { + var expandText: String + var expandTextColor: UIColor + var numberOfCompactLines: Int + var textColor: UIColor + var textFont: UIFont + + init(expandText: String = "…more", + expandTextColor: UIColor = .blue, + numberOfCompactLines: Int = 2, + textColor: UIColor = .darkText, + textFont: UIFont = .preferredFont(forTextStyle: .body)) { + self.expandText = expandText + self.expandTextColor = expandTextColor + self.numberOfCompactLines = numberOfCompactLines + self.textColor = textColor + self.textFont = textFont } - - init() {} - - var expandText = Default.expandText - var expandTextColor = Default.expandTextColor - var numberOfCompactLines = Default.numberOfCompactLines - var textColor = Default.textColor - var textFont = Default.textFont } diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCReviewCell.swift b/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCReviewCell.swift index c11cf1bcc9..b4776b6c0f 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCReviewCell.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCReviewCell.swift @@ -22,20 +22,22 @@ final class UGCReviewCell: MWMTableViewCell { } } - @IBOutlet private weak var reviewLabel: ExpandableTextView! { + @IBOutlet private weak var reviewLabel: ExpandableReviewView! { didSet { - reviewLabel.textFont = UIFont.regular14() - reviewLabel.textColor = UIColor.blackPrimaryText() - reviewLabel.expandText = L("placepage_more_button") - reviewLabel.expandTextColor = UIColor.linkBlue() + let settings = ExpandableReviewSettings(expandText: L("placepage_more_button"), + expandTextColor: .linkBlue(), + textColor: .blackPrimaryText(), + textFont: .regular14()) + reviewLabel.apply(settings: settings) } } - @objc func config(review: UGCReview, onUpdate: @escaping () -> Void) { + @objc func config(review: UGCReview, isExpanded: Bool, onUpdate: @escaping () -> Void) { titleLabel.text = review.title dateLabel.text = review.date - reviewLabel.text = review.text - reviewLabel.onUpdate = onUpdate + reviewLabel.configure(text: review.text, + isExpanded: isExpanded, + onUpdate: onUpdate) ratingView.value = review.rating.value ratingView.type = review.rating.type isSeparatorHidden = true diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCReviewCell.xib b/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCReviewCell.xib index bfe461c231..75b15b8ef5 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCReviewCell.xib +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCReviewCell.xib @@ -1,11 +1,11 @@ - + - + @@ -41,8 +41,8 @@ - - + + diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCYourReviewCell.swift b/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCYourReviewCell.swift index c373df5700..6e197924c6 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCYourReviewCell.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCYourReviewCell.swift @@ -19,12 +19,13 @@ final class UGCYourReviewCell: MWMTableViewCell { } } - @IBOutlet private weak var reviewLabel: ExpandableTextView! { + @IBOutlet private weak var reviewLabel: ExpandableReviewView! { didSet { - reviewLabel.textFont = UIFont.regular14() - reviewLabel.textColor = UIColor.blackPrimaryText() - reviewLabel.expandText = L("placepage_more_button") - reviewLabel.expandTextColor = UIColor.linkBlue() + let settings = ExpandableReviewSettings(expandText: L("placepage_more_button"), + expandTextColor: .linkBlue(), + textColor: .blackPrimaryText(), + textFont: .regular14()) + reviewLabel.apply(settings: settings) } } @@ -45,12 +46,13 @@ final class UGCYourReviewCell: MWMTableViewCell { } } - @objc func config(yourReview: UGCYourReview, onUpdate: @escaping () -> Void) { + @objc func config(yourReview: UGCYourReview, isExpanded: Bool, onUpdate: @escaping () -> Void) { dateLabel.text = yourReview.date self.yourReview = yourReview - reviewLabel.text = yourReview.text + reviewLabel.configure(text: yourReview.text, + isExpanded: isExpanded, + onUpdate: onUpdate) reviewBottomOffset.constant = yourReview.text.isEmpty ? 0 : Config.defaultReviewBottomOffset - reviewLabel.onUpdate = onUpdate updateCollectionView() isSeparatorHidden = true } diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCYourReviewCell.xib b/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCYourReviewCell.xib index c1f572c86e..f3f152fe9c 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCYourReviewCell.xib +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/Content/UGC/UGCYourReviewCell.xib @@ -1,11 +1,11 @@ - + - + @@ -19,7 +19,7 @@ - + - - + + - + @@ -64,7 +64,7 @@ - + diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/MWMPlacePageLayout.mm b/iphone/Maps/UI/PlacePage/PlacePageLayout/MWMPlacePageLayout.mm index a02cff38b4..1559993b8e 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/MWMPlacePageLayout.mm +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/MWMPlacePageLayout.mm @@ -555,8 +555,13 @@ map const kMetaInfoCells = { Class cls = [MWMUGCYourReviewCell class]; auto c = static_cast( [tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); - [c configWithYourReview:static_cast([ugc reviewWithIndex:indexPath.row]) - onUpdate:onUpdate]; + auto review = static_cast([ugc reviewWithIndex:indexPath.row]); + [c configWithYourReview:review + isExpanded:[ugc isExpanded:review] + onUpdate:^{ + [ugc markExpanded:review]; + onUpdate(); + }]; return c; } case ReviewRow::Review: @@ -564,8 +569,13 @@ map const kMetaInfoCells = { Class cls = [MWMUGCReviewCell class]; auto c = static_cast( [tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); - [c configWithReview:static_cast([ugc reviewWithIndex:indexPath.row]) - onUpdate:onUpdate]; + auto review = static_cast([ugc reviewWithIndex:indexPath.row]); + [c configWithReview:review + isExpanded:[ugc isExpanded:review] + onUpdate:^{ + [ugc markExpanded:review]; + onUpdate(); + }]; return c; } case ReviewRow::MoreReviews: diff --git a/iphone/Maps/UI/PlacePage/UGCViewModel/MWMUGCViewModel.mm b/iphone/Maps/UI/PlacePage/UGCViewModel/MWMUGCViewModel.mm index 05f405bd94..1660804ae7 100644 --- a/iphone/Maps/UI/PlacePage/UGCViewModel/MWMUGCViewModel.mm +++ b/iphone/Maps/UI/PlacePage/UGCViewModel/MWMUGCViewModel.mm @@ -30,6 +30,7 @@ MWMUGCRatingValueType * ratingValueType(float rating) @interface MWMUGCViewModel () @property(copy, nonatomic) MWMVoidBlock refreshCallback; +@property(strong, nonatomic) NSMutableDictionary *expandedReviews; @end @implementation MWMUGCViewModel @@ -44,6 +45,7 @@ MWMUGCRatingValueType * ratingValueType(float rating) self = [super init]; if (self) { + _expandedReviews = [[NSMutableDictionary alloc] initWithCapacity: 1]; m_ugc = ugc; m_ugcUpdate = update; [self fillReviewRows]; @@ -113,6 +115,16 @@ MWMUGCRatingValueType * ratingValueType(float rating) rating:ratingValueType(review.m_rating)]; } +- (BOOL)isExpanded:(id _Nonnull) review { + NSString *key = [[NSString alloc] initWithFormat:@"%@%lu", review.date, (unsigned long)[review.text hash]]; + return _expandedReviews[key] == nil ? NO : YES; +} + +- (void)markExpanded:(id _Nonnull) review { + NSString *key = [[NSString alloc] initWithFormat:@"%@%lu", review.date, (unsigned long)[review.text hash]]; + _expandedReviews[key] = @YES; +} + #pragma mark - Propertis - (NSString *)reviewDate:(ugc::Time const &) time diff --git a/iphone/Maps/UI/Reviews/MWMReviewsViewModelProtocol.h b/iphone/Maps/UI/Reviews/MWMReviewsViewModelProtocol.h index 6fc4c1dfbe..9c6a09bc9c 100644 --- a/iphone/Maps/UI/Reviews/MWMReviewsViewModelProtocol.h +++ b/iphone/Maps/UI/Reviews/MWMReviewsViewModelProtocol.h @@ -3,4 +3,6 @@ @protocol MWMReviewsViewModelProtocol - (NSInteger)numberOfReviews; - (id _Nonnull)reviewWithIndex:(NSInteger)index; +- (BOOL)isExpanded:(id _Nonnull) review; +- (void)markExpanded:(id _Nonnull) review; @end diff --git a/iphone/Maps/UI/Reviews/ReviewsViewController.swift b/iphone/Maps/UI/Reviews/ReviewsViewController.swift index 4d06a196ac..199d1577be 100644 --- a/iphone/Maps/UI/Reviews/ReviewsViewController.swift +++ b/iphone/Maps/UI/Reviews/ReviewsViewController.swift @@ -15,6 +15,11 @@ final class ReviewsViewController: MWMTableViewController { super.viewDidLoad() registerCells() } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + tableView.refresh() + } private func registerCells() { [UGCYourReviewCell.self, UGCReviewCell.self].forEach { @@ -31,11 +36,23 @@ final class ReviewsViewController: MWMTableViewController { switch cellModel { case let cellModel as UGCYourReview: let cell = tableView.dequeueReusableCell(withCellClass: UGCYourReviewCell.self, indexPath: indexPath) as! UGCYourReviewCell - cell.config(yourReview: cellModel, onUpdate: tableView.refresh) + cell.config(yourReview: cellModel, + isExpanded: viewModel.isExpanded(cellModel), + onUpdate: { [weak self] in + guard let self = self else { return } + self.viewModel.markExpanded(cellModel) + self.tableView.refresh() + }) return cell case let cellModel as UGCReview: let cell = tableView.dequeueReusableCell(withCellClass: UGCReviewCell.self, indexPath: indexPath) as! UGCReviewCell - cell.config(review: cellModel, onUpdate: tableView.refresh) + cell.config(review: cellModel, + isExpanded: viewModel.isExpanded(cellModel), + onUpdate: { [weak self] in + guard let self = self else { return } + self.viewModel.markExpanded(cellModel) + self.tableView.refresh() + }) return cell default: assert(false) }