[iOS] fixed MAPSME-9555 app is crashing when open through review push.

This commit is contained in:
Zoia Pribytkova 2019-02-28 16:22:22 +03:00 committed by Aleksey Belousov
parent 84d0f50159
commit 88f010d3d5
10 changed files with 196 additions and 213 deletions

View file

@ -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
}
}
}
}

View file

@ -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
}

View file

@ -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

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -41,8 +41,8 @@
<constraint firstAttribute="width" constant="48" placeholder="YES" id="TVO-Dx-h45"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="762-NT-BKz" customClass="ExpandableTextView" customModule="maps_me" customModuleProvider="target">
<rect key="frame" x="16" y="78" width="288" height="40"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="762-NT-BKz" customClass="ExpandableReviewView" customModule="maps_me" customModuleProvider="target">
<rect key="frame" x="16" y="77.5" width="288" height="40.5"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d1v-ce-wSZ">

View file

@ -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
}

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -19,7 +19,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="M3q-L0-4Y7" propertyAccessControl="all">
<rect key="frame" x="0.0" y="0.0" width="320" height="135"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="134.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" verticalCompressionResistancePriority="751" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nht-6C-S3i">
<rect key="frame" x="16" y="16" width="42" height="20.5"/>
@ -33,12 +33,12 @@
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="G4x-pj-fmv" customClass="ExpandableTextView" customModule="maps_me" customModuleProvider="target">
<rect key="frame" x="16" y="78" width="288" height="40"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="G4x-pj-fmv" customClass="ExpandableReviewView" customModule="maps_me" customModuleProvider="target">
<rect key="frame" x="16" y="77.5" width="288" height="40"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aOE-yo-hUQ">
<rect key="frame" x="16" y="134" width="304" height="1"/>
<rect key="frame" x="16" y="133.5" width="304" height="1"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="E6L-PS-9ij"/>
@ -64,7 +64,7 @@
</constraints>
</view>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" bounces="NO" pagingEnabled="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="none" prefetchingEnabled="NO" translatesAutoresizingMaskIntoConstraints="NO" id="w6j-GC-6Bv">
<rect key="frame" x="0.0" y="135" width="320" height="55.5"/>
<rect key="frame" x="0.0" y="134.5" width="320" height="56"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" priority="999" constant="56" id="2hn-mr-UAw"/>

View file

@ -555,8 +555,13 @@ map<MetainfoRows, Class> const kMetaInfoCells = {
Class cls = [MWMUGCYourReviewCell class];
auto c = static_cast<MWMUGCYourReviewCell *>(
[tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]);
[c configWithYourReview:static_cast<MWMUGCYourReview *>([ugc reviewWithIndex:indexPath.row])
onUpdate:onUpdate];
auto review = static_cast<MWMUGCYourReview *>([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<MetainfoRows, Class> const kMetaInfoCells = {
Class cls = [MWMUGCReviewCell class];
auto c = static_cast<MWMUGCReviewCell *>(
[tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]);
[c configWithReview:static_cast<MWMUGCReview *>([ugc reviewWithIndex:indexPath.row])
onUpdate:onUpdate];
auto review = static_cast<MWMUGCReview *>([ugc reviewWithIndex:indexPath.row]);
[c configWithReview:review
isExpanded:[ugc isExpanded:review]
onUpdate:^{
[ugc markExpanded:review];
onUpdate();
}];
return c;
}
case ReviewRow::MoreReviews:

View file

@ -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<MWMReviewProtocol> _Nonnull) review {
NSString *key = [[NSString alloc] initWithFormat:@"%@%lu", review.date, (unsigned long)[review.text hash]];
return _expandedReviews[key] == nil ? NO : YES;
}
- (void)markExpanded:(id<MWMReviewProtocol> _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

View file

@ -3,4 +3,6 @@
@protocol MWMReviewsViewModelProtocol
- (NSInteger)numberOfReviews;
- (id<MWMReviewProtocol> _Nonnull)reviewWithIndex:(NSInteger)index;
- (BOOL)isExpanded:(id<MWMReviewProtocol> _Nonnull) review;
- (void)markExpanded:(id<MWMReviewProtocol> _Nonnull) review;
@end

View file

@ -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)
}