549 lines
20 KiB
Swift
549 lines
20 KiB
Swift
//
|
|
// GroupItineraryView.swift
|
|
// QuickLocation
|
|
//
|
|
|
|
import UIKit
|
|
import RxSwift
|
|
import RxCocoa
|
|
import SwiftDate
|
|
|
|
/// 圈子 Tab 下的「行程」内容页
|
|
final class GroupItineraryView: UIView {
|
|
|
|
var disposeBag = DisposeBag()
|
|
|
|
var onSwitchGroup: (() -> Void)?
|
|
var onDeleteSchedule: ((ScheduleModel) -> Void)?
|
|
var onSelectSchedule: ((ScheduleModel) -> Void)?
|
|
|
|
func updateGroupName(_ name: String) {
|
|
groupNameLab.text = name.isEmpty ? " " : name
|
|
}
|
|
|
|
func reloadSchedules(_ list: [ScheduleModel]) {
|
|
scheduleSections = Self.groupByDay(list)
|
|
tableView.reloadData()
|
|
tableView.refresh(status: .noMoreData, isEmpty: list.isEmpty)
|
|
}
|
|
|
|
func updateMembers(_ members: [GroupMemberModel]) {
|
|
relationIdxByUserId = members.reduce(into: [:]) { result, member in
|
|
guard !member.user_id.isEmpty else { return }
|
|
result[member.user_id] = member.extra.relation_idx
|
|
}
|
|
tableView.reloadData()
|
|
}
|
|
|
|
private var scheduleSections: [(dayText: String, dateText: String, items: [ScheduleModel])] = []
|
|
private var relationIdxByUserId: [String: String] = [:]
|
|
|
|
private func setupUI() {
|
|
addSubview(headerRow)
|
|
headerRow.addSubview(groupIcon)
|
|
headerRow.addSubview(groupNameLab)
|
|
headerRow.addSubview(changeGroupBtn)
|
|
addSubview(tableView)
|
|
addSubview(createBtn)
|
|
|
|
headerRow.layoutChain
|
|
.top(8)
|
|
.edgesHorzontal(16)
|
|
.height(28)
|
|
|
|
groupIcon.layoutChain
|
|
.left()
|
|
.centerY()
|
|
.width(22)
|
|
.height(22)
|
|
|
|
changeGroupBtn.layoutChain
|
|
.right()
|
|
.centerY()
|
|
.height(28)
|
|
|
|
groupNameLab.layoutChain
|
|
.leftToRightOfView(groupIcon, offset: 8)
|
|
.centerY()
|
|
.rightToLeftOfView(changeGroupBtn, offset: -8, relation: .lessThanOrEqual)
|
|
|
|
tableView.layoutChain
|
|
.topToBottomOfView(headerRow, offset: 12)
|
|
.edgesHorzontal()
|
|
.bottom()
|
|
|
|
createBtn.layoutChain
|
|
.bottom(kSafeBottomMargin + 102)
|
|
.right(20)
|
|
.width(54)
|
|
.heightToWidth(1)
|
|
}
|
|
|
|
private func setupRx() {
|
|
createBtn.rx.tap.subscribe(onNext: { _ in
|
|
AppRouter.push(Route.createSchedule)
|
|
}).disposed(by: disposeBag)
|
|
|
|
changeGroupBtn.rx.tap
|
|
.subscribe(onNext: { [weak self] in self?.onSwitchGroup?() })
|
|
.disposed(by: disposeBag)
|
|
}
|
|
|
|
private static func groupByDay(_ list: [ScheduleModel]) -> [(dayText: String, dateText: String, items: [ScheduleModel])] {
|
|
let sorted = list.sorted { $0.timestamp > $1.timestamp }
|
|
var map: [String: [ScheduleModel]] = [:]
|
|
var order: [String] = []
|
|
var dayDateMap: [String: (dayText: String, dateText: String)] = [:]
|
|
let fmt = DateFormatter()
|
|
fmt.locale = Locale(identifier: "zh_CN")
|
|
let calendar = Calendar.current
|
|
for item in sorted {
|
|
let date = Date(timeIntervalSince1970: TimeInterval(item.timestamp) / 1000)
|
|
fmt.dateFormat = "M月d日"
|
|
let dateText = fmt.string(from: date)
|
|
let dayText: String
|
|
if calendar.isDateInToday(date) {
|
|
dayText = "今天"
|
|
} else if calendar.isDateInYesterday(date) {
|
|
dayText = "昨天"
|
|
} else {
|
|
fmt.dateFormat = "EEEE"
|
|
dayText = fmt.string(from: date)
|
|
}
|
|
let key = "\(dayText) \(dateText)"
|
|
if map[key] == nil {
|
|
order.append(key)
|
|
dayDateMap[key] = (dayText, dateText)
|
|
map[key] = []
|
|
}
|
|
map[key]?.append(item)
|
|
}
|
|
return order.map { key in
|
|
let pair = dayDateMap[key] ?? ("", "")
|
|
return (pair.dayText, pair.dateText, map[key] ?? [])
|
|
}
|
|
}
|
|
|
|
lazy var headerRow: UIView = {
|
|
let v = UIView()
|
|
v.backgroundColor = .clear
|
|
return v
|
|
}()
|
|
|
|
lazy var groupIcon: UIImageView = {
|
|
let iv = UIImageView(image: UIImage(named: "Home/group_name_icon"))
|
|
iv.contentMode = .scaleAspectFit
|
|
return iv
|
|
}()
|
|
|
|
lazy var groupNameLab: UILabel = {
|
|
let label = UILabel()
|
|
label.font = .systemFont(ofSize: 16, weight: .bold)
|
|
label.textColor = UIColor(hexStr: "#353B4F")
|
|
label.text = " "
|
|
return label
|
|
}()
|
|
|
|
lazy var changeGroupBtn: UIButton = {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setTitle("切换圈子 ", for: .normal)
|
|
btn.setTitleColor(UIColor(hexStr: "#353B4F"), for: .normal)
|
|
btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
|
btn.setImage(UIImage(named: "Common/arrow_right"), for: .normal)
|
|
btn.semanticContentAttribute = .forceRightToLeft
|
|
return btn
|
|
}()
|
|
|
|
lazy var tableView: UITableView = {
|
|
let tv = UITableView(frame: .zero, style: .grouped)
|
|
tv.backgroundColor = UIColor(hexStr: "#FAFAFA")
|
|
tv.separatorStyle = .none
|
|
tv.showsVerticalScrollIndicator = false
|
|
tv.register(GroupItineraryCell.self)
|
|
tv.dataSource = self
|
|
tv.delegate = self
|
|
tv.estimatedRowHeight = 100
|
|
tv.rowHeight = UITableView.automaticDimension
|
|
tv.sectionHeaderHeight = 36
|
|
tv.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
|
|
if #available(iOS 15.0, *) {
|
|
tv.sectionHeaderTopPadding = 0
|
|
}
|
|
tv.dl.addEmptyDataSet { emptyDataSet in
|
|
emptyDataSet.emptyNoDataTitle = "这里怎么什么都没有~"
|
|
emptyDataSet.emptyNoDataDetail = ""
|
|
emptyDataSet.customButtonTitle = ""
|
|
emptyDataSet.backgroundColor = .clear
|
|
emptyDataSet.offsetY = -100
|
|
}
|
|
return tv
|
|
}()
|
|
|
|
lazy var createBtn: UIButton = {
|
|
let btn = UIButton()
|
|
btn.setImage(UIImage(named: "Schedule/create"), for: .normal)
|
|
return btn
|
|
}()
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
backgroundColor = .clear
|
|
setupUI()
|
|
setupRx()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
}
|
|
|
|
// MARK: - UITableView
|
|
extension GroupItineraryView: UITableViewDataSource, UITableViewDelegate {
|
|
func numberOfSections(in tableView: UITableView) -> Int {
|
|
scheduleSections.count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
scheduleSections[section].items.count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
|
let header = UIView()
|
|
header.backgroundColor = .clear
|
|
let sectionData = scheduleSections[section]
|
|
let label = UILabel()
|
|
let color = UIColor(hexStr: "#353B4F")
|
|
let attr = NSMutableAttributedString()
|
|
attr.append(NSAttributedString(
|
|
string: sectionData.dayText,
|
|
attributes: [
|
|
.font: UIFont.systemFont(ofSize: 16, weight: .bold),
|
|
.foregroundColor: color
|
|
]
|
|
))
|
|
if !sectionData.dateText.isEmpty {
|
|
attr.append(NSAttributedString(
|
|
string: " \(sectionData.dateText)",
|
|
attributes: [
|
|
.font: UIFont.systemFont(ofSize: 13, weight: .medium),
|
|
.foregroundColor: color
|
|
]
|
|
))
|
|
}
|
|
label.attributedText = attr
|
|
header.addSubview(label)
|
|
label.layoutChain.left(16).centerY()
|
|
return header
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
|
.leastNormalMagnitude
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
|
UIView()
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
let cell: GroupItineraryCell = tableView.dequeueReusableCell(for: indexPath)
|
|
let model = scheduleSections[indexPath.section].items[indexPath.row]
|
|
cell.configure(model, relationIdx: relationIdxByUserId[model.creator_id] ?? "")
|
|
return cell
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
onSelectSchedule?(scheduleSections[indexPath.section].items[indexPath.row])
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - GroupItineraryCell
|
|
final class GroupItineraryCell: UITableViewCell {
|
|
|
|
func configure(_ model: ScheduleModel, relationIdx: String) {
|
|
avatarImg.image = model.userIcon
|
|
nameLab.text = model.nick_name.isEmpty ? "未命名用户" : model.nick_name
|
|
relationIcon.configure(relationIdx: relationIdx)
|
|
|
|
let points = Self.orderedPoints(model.points)
|
|
startLocationLab.text = Self.locationName(points.first)
|
|
endLocationLab.text = Self.locationName(points.last, maximumCharacters: 4)
|
|
durationLab.text = Self.durationText(from: points.first, to: points.last)
|
|
configureTags(model.groups.map(\.group_name).filter { !$0.isEmpty })
|
|
}
|
|
|
|
override init(style: CellStyle, reuseIdentifier: String?) {
|
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
|
selectionStyle = .none
|
|
backgroundColor = .clear
|
|
contentView.backgroundColor = .clear
|
|
|
|
contentView.addSubview(cardView)
|
|
cardView.addSubview(avatarImg)
|
|
cardView.addSubview(nameLab)
|
|
cardView.addSubview(relationIcon)
|
|
cardView.addSubview(startMarkerLab)
|
|
cardView.addSubview(startLocationLab)
|
|
cardView.addSubview(routeLineView)
|
|
cardView.addSubview(durationLab)
|
|
cardView.addSubview(endMarkerLab)
|
|
cardView.addSubview(endLocationLab)
|
|
cardView.addSubview(tagScrollView)
|
|
tagScrollView.addSubview(tagStackView)
|
|
|
|
cardView.layoutChain
|
|
.edgesVertical(6)
|
|
.edgesHorzontal(15)
|
|
|
|
avatarImg.layoutChain
|
|
.top(12)
|
|
.left(14)
|
|
.width(28)
|
|
.height(28)
|
|
|
|
nameLab.layoutChain
|
|
.leftToRightOfView(avatarImg, offset: 8)
|
|
.centerY(avatarImg)
|
|
|
|
relationIcon.layoutChain
|
|
.leftToRightOfView(nameLab, offset: 7)
|
|
.centerY(nameLab)
|
|
.width(20)
|
|
.height(20)
|
|
|
|
relationIcon.translatesAutoresizingMaskIntoConstraints = false
|
|
relationIcon.trailingAnchor.constraint(lessThanOrEqualTo: cardView.trailingAnchor, constant: -14).isActive = true
|
|
|
|
startMarkerLab.translatesAutoresizingMaskIntoConstraints = false
|
|
startLocationLab.translatesAutoresizingMaskIntoConstraints = false
|
|
routeLineView.translatesAutoresizingMaskIntoConstraints = false
|
|
durationLab.translatesAutoresizingMaskIntoConstraints = false
|
|
endMarkerLab.translatesAutoresizingMaskIntoConstraints = false
|
|
endLocationLab.translatesAutoresizingMaskIntoConstraints = false
|
|
tagScrollView.translatesAutoresizingMaskIntoConstraints = false
|
|
tagStackView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
NSLayoutConstraint.activate([
|
|
startMarkerLab.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 14),
|
|
startMarkerLab.topAnchor.constraint(equalTo: avatarImg.bottomAnchor, constant: 10),
|
|
startMarkerLab.widthAnchor.constraint(equalToConstant: 20),
|
|
startMarkerLab.heightAnchor.constraint(equalToConstant: 20),
|
|
|
|
startLocationLab.leadingAnchor.constraint(equalTo: startMarkerLab.trailingAnchor, constant: 7),
|
|
startLocationLab.centerYAnchor.constraint(equalTo: startMarkerLab.centerYAnchor),
|
|
startLocationLab.widthAnchor.constraint(lessThanOrEqualToConstant: 82),
|
|
|
|
endLocationLab.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -14),
|
|
endLocationLab.centerYAnchor.constraint(equalTo: startMarkerLab.centerYAnchor),
|
|
endLocationLab.widthAnchor.constraint(lessThanOrEqualToConstant: 82),
|
|
|
|
endMarkerLab.trailingAnchor.constraint(equalTo: endLocationLab.leadingAnchor, constant: -7),
|
|
endMarkerLab.centerYAnchor.constraint(equalTo: startMarkerLab.centerYAnchor),
|
|
endMarkerLab.widthAnchor.constraint(equalToConstant: 20),
|
|
endMarkerLab.heightAnchor.constraint(equalToConstant: 20),
|
|
|
|
routeLineView.leadingAnchor.constraint(equalTo: startLocationLab.trailingAnchor, constant: 8),
|
|
routeLineView.trailingAnchor.constraint(equalTo: endMarkerLab.leadingAnchor, constant: -8),
|
|
routeLineView.centerYAnchor.constraint(equalTo: startMarkerLab.centerYAnchor, constant: 4),
|
|
routeLineView.heightAnchor.constraint(equalToConstant: 1),
|
|
routeLineView.widthAnchor.constraint(greaterThanOrEqualToConstant: 28),
|
|
|
|
durationLab.centerXAnchor.constraint(equalTo: routeLineView.centerXAnchor),
|
|
durationLab.bottomAnchor.constraint(equalTo: routeLineView.topAnchor, constant: -3),
|
|
durationLab.widthAnchor.constraint(lessThanOrEqualTo: routeLineView.widthAnchor, constant: 24),
|
|
|
|
tagScrollView.topAnchor.constraint(equalTo: startMarkerLab.bottomAnchor, constant: 8),
|
|
tagScrollView.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 90),
|
|
tagScrollView.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -14),
|
|
tagScrollView.heightAnchor.constraint(equalToConstant: 18),
|
|
tagScrollView.bottomAnchor.constraint(equalTo: cardView.bottomAnchor, constant: -10),
|
|
|
|
tagStackView.leadingAnchor.constraint(equalTo: tagScrollView.contentLayoutGuide.leadingAnchor),
|
|
tagStackView.trailingAnchor.constraint(equalTo: tagScrollView.contentLayoutGuide.trailingAnchor),
|
|
tagStackView.topAnchor.constraint(equalTo: tagScrollView.contentLayoutGuide.topAnchor),
|
|
tagStackView.bottomAnchor.constraint(equalTo: tagScrollView.contentLayoutGuide.bottomAnchor),
|
|
tagStackView.heightAnchor.constraint(equalTo: tagScrollView.frameLayoutGuide.heightAnchor),
|
|
tagStackView.widthAnchor.constraint(greaterThanOrEqualTo: tagScrollView.frameLayoutGuide.widthAnchor)
|
|
])
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
private lazy var cardView: UIView = {
|
|
let v = UIView()
|
|
v.backgroundColor = .white
|
|
v.cornerRadius = 16
|
|
return v
|
|
}()
|
|
|
|
private lazy var avatarImg: UIImageView = {
|
|
let iv = UIImageView()
|
|
iv.contentMode = .scaleAspectFill
|
|
iv.cornerRadius = 10
|
|
iv.clipsToBounds = true
|
|
return iv
|
|
}()
|
|
|
|
private lazy var nameLab: UILabel = {
|
|
let label = UILabel()
|
|
label.font = .systemFont(ofSize: 15, weight: .bold)
|
|
label.textColor = UIColor(hexStr: "#353B4F")
|
|
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
return label
|
|
}()
|
|
|
|
private lazy var relationIcon = RelationIconImageView()
|
|
|
|
override func prepareForReuse() {
|
|
super.prepareForReuse()
|
|
relationIcon.clear()
|
|
}
|
|
|
|
private lazy var startMarkerLab: UILabel = makeMarkerLabel(text: "始")
|
|
private lazy var endMarkerLab: UILabel = makeMarkerLabel(text: "终")
|
|
|
|
private func makeMarkerLabel(text: String) -> UILabel {
|
|
let label = UILabel()
|
|
label.text = text
|
|
label.font = FontManager.youSheBiaoTiHei(11)
|
|
label.textColor = UIColor(hexStr: "#58EDFF")
|
|
label.textAlignment = .center
|
|
label.backgroundColor = UIColor(hexStr: "#293445")
|
|
label.cornerRadius = 7
|
|
label.clipsToBounds = true
|
|
return label
|
|
}
|
|
|
|
private lazy var startLocationLab: UILabel = makeLocationLabel()
|
|
private lazy var endLocationLab: UILabel = makeLocationLabel()
|
|
|
|
private func makeLocationLabel() -> UILabel {
|
|
let label = UILabel()
|
|
label.font = .systemFont(ofSize: 14, weight: .medium)
|
|
label.textColor = UIColor(hexStr: "#353B4F")
|
|
label.lineBreakMode = .byTruncatingTail
|
|
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
return label
|
|
}
|
|
|
|
private lazy var durationLab: UILabel = {
|
|
let label = UILabel()
|
|
label.font = .systemFont(ofSize: 12, weight: .regular)
|
|
label.textColor = UIColor(hexStr: "#888888")
|
|
label.textAlignment = .center
|
|
label.backgroundColor = .white
|
|
label.lineBreakMode = .byTruncatingTail
|
|
return label
|
|
}()
|
|
|
|
private lazy var routeLineView: GroupItineraryDashLineView = {
|
|
GroupItineraryDashLineView()
|
|
}()
|
|
|
|
private lazy var tagScrollView: UIScrollView = {
|
|
let view = UIScrollView()
|
|
view.showsHorizontalScrollIndicator = false
|
|
view.alwaysBounceHorizontal = false
|
|
return view
|
|
}()
|
|
|
|
private lazy var tagStackView: UIStackView = {
|
|
let view = UIStackView()
|
|
view.axis = .horizontal
|
|
view.alignment = .fill
|
|
view.spacing = 6
|
|
view.addArrangedSubview(tagSpacerView)
|
|
return view
|
|
}()
|
|
|
|
private lazy var tagSpacerView: UIView = {
|
|
let view = UIView()
|
|
view.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
return view
|
|
}()
|
|
|
|
private func configureTags(_ names: [String]) {
|
|
tagStackView.arrangedSubviews.filter { $0 !== tagSpacerView }.forEach {
|
|
tagStackView.removeArrangedSubview($0)
|
|
$0.removeFromSuperview()
|
|
}
|
|
for name in names {
|
|
let label = PaddingLabel()
|
|
label.text = name
|
|
label.insets = UIEdgeInsets(top: 2, left: 7, bottom: 2, right: 7)
|
|
label.font = .systemFont(ofSize: 10, weight: .medium)
|
|
label.textColor = UIColor(hexStr: "#D5923F")
|
|
label.backgroundColor = UIColor(hexStr: "#FFF9DD")
|
|
label.cornerRadius = 4
|
|
label.clipsToBounds = true
|
|
label.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
tagStackView.addArrangedSubview(label)
|
|
}
|
|
tagScrollView.isHidden = names.isEmpty
|
|
}
|
|
|
|
private static func orderedPoints(_ points: [SchedulePointModel]) -> [SchedulePointModel] {
|
|
guard points.count > 1 else { return points }
|
|
let sequences = points.map(\.sequence)
|
|
guard Set(sequences).count == points.count else { return points }
|
|
return points.sorted { $0.sequence < $1.sequence }
|
|
}
|
|
|
|
private static func locationName(_ point: SchedulePointModel?, maximumCharacters: Int? = nil) -> String {
|
|
let name: String
|
|
if let point, !point.street.isEmpty {
|
|
name = point.street
|
|
} else if let point, !point.formatted_address.isEmpty {
|
|
name = point.formatted_address
|
|
} else {
|
|
name = "暂无地点"
|
|
}
|
|
guard let maximumCharacters, name.count > maximumCharacters else { return name }
|
|
return String(name.prefix(maximumCharacters)) + "..."
|
|
}
|
|
|
|
private static func durationText(from start: SchedulePointModel?, to end: SchedulePointModel?) -> String {
|
|
guard let start, let end,
|
|
start.id != end.id,
|
|
end.expected_timestamp > start.expected_timestamp else { return "--" }
|
|
let minutes = max(1, (end.expected_timestamp - start.expected_timestamp) / 60_000)
|
|
let hours = minutes / 60
|
|
let remainder = minutes % 60
|
|
return hours > 0 ? "\(hours)小时\(remainder)分" : "\(minutes)分钟"
|
|
}
|
|
}
|
|
|
|
private final class GroupItineraryDashLineView: UIView {
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
layer.sublayers?.forEach { if $0 is CAShapeLayer { $0.removeFromSuperlayer() } }
|
|
|
|
let line = CAShapeLayer()
|
|
line.strokeColor = UIColor(hexStr: "#D6D6D6").cgColor
|
|
line.lineWidth = 1.5
|
|
line.lineDashPattern = [6, 6]
|
|
let path = UIBezierPath()
|
|
path.move(to: CGPoint(x: 0, y: bounds.midY))
|
|
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.midY))
|
|
line.path = path.cgPath
|
|
layer.addSublayer(line)
|
|
}
|
|
}
|
|
|
|
private final class PaddingLabel: UILabel {
|
|
var insets = UIEdgeInsets.zero
|
|
|
|
override func drawText(in rect: CGRect) {
|
|
super.drawText(in: rect.inset(by: insets))
|
|
}
|
|
|
|
override var intrinsicContentSize: CGSize {
|
|
let size = super.intrinsicContentSize
|
|
return CGSize(width: size.width + insets.left + insets.right,
|
|
height: size.height + insets.top + insets.bottom)
|
|
}
|
|
}
|