Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,15 @@
final class BookmarksListBuilder {
static func build(markGroupId: MWMMarkGroupID,
bookmarksCoordinator: BookmarksCoordinator?,
delegate: BookmarksListDelegate? = nil) -> BookmarksListViewController {
let viewController = BookmarksListViewController()
let router = BookmarksListRouter(MapViewController.shared()!, bookmarksCoordinator: bookmarksCoordinator)
let interactor = BookmarksListInteractor(markGroupId: markGroupId)
let presenter = BookmarksListPresenter(view: viewController,
router: router,
delegate: delegate,
interactor: interactor)
viewController.presenter = presenter
return viewController
}
}

View file

@ -0,0 +1,60 @@
protocol BookmarksListInfoViewControllerDelegate: AnyObject {
func didPressDescription()
func didUpdateContent()
}
final class BookmarksListInfoViewController: UIViewController {
var info: IBookmarksListInfoViewModel? {
didSet {
guard isViewLoaded, let info = info else { return }
updateInfo(info)
}
}
weak var delegate: BookmarksListInfoViewControllerDelegate?
@IBOutlet private var titleImageView: UIImageView!
@IBOutlet private var titleLabel: UILabel!
@IBOutlet private var descriptionButton: UIButton!
@IBOutlet private var authorContainerView: UIView!
@IBOutlet private var infoStack: UIStackView!
@IBOutlet private var separatorsConstraints: [NSLayoutConstraint]!
@IBAction private func onDescription(_ sender: UIButton) {
delegate?.didPressDescription()
}
override func viewDidLoad() {
super.viewDidLoad()
separatorsConstraints.forEach { $0.constant = 1 / UIScreen.main.scale }
descriptionButton.titleLabel?.numberOfLines = 2
guard let info = info else { return }
updateInfo(info)
}
private func updateInfo(_ info: IBookmarksListInfoViewModel) {
titleLabel.text = info.title
descriptionButton.isHidden = !info.hasDescription
if info.hasDescription {
let description = info.isHtmlDescription
? BookmarksListInfoViewController.getPlainText(info.description)
: info.description
descriptionButton.setTitle(description, for: .normal)
}
titleImageView.isHidden = true
if let imageUrl = info.imageUrl {
titleImageView.wi_setImage(with: imageUrl, transitionDuration: 0) { [weak self] (image, error) in
guard image != nil else { return }
self?.titleImageView.isHidden = false
self?.delegate?.didUpdateContent()
}
}
}
private static func getPlainText(_ htmlText: String) -> String? {
let formattedText = NSAttributedString.string(withHtml: htmlText, defaultAttributes: [:])
return formattedText?.string
}
}

View file

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="BookmarksListInfoViewController" customModule="CoMaps" customModuleProvider="target">
<connections>
<outlet property="authorContainerView" destination="bit-FN-ytL" id="CAI-TP-A5D"/>
<outlet property="descriptionButton" destination="cxc-pt-qei" id="zce-so-aHF"/>
<outlet property="infoStack" destination="226-gO-8GN" id="f2A-eJ-FGG"/>
<outlet property="titleImageView" destination="dsB-Xh-jOl" id="rsb-lH-MDN"/>
<outlet property="titleLabel" destination="TFR-Qj-pUF" id="D2T-y8-ztA"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
<outletCollection property="separatorsConstraints" destination="GqW-4j-7uM" collectionClass="NSMutableArray" id="wXt-eX-9BJ"/>
<outletCollection property="separatorsConstraints" destination="soU-hC-iDQ" collectionClass="NSMutableArray" id="bQl-DL-hUh"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="375" height="340"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="KGV-R1-hoS">
<rect key="frame" x="0.0" y="0.0" width="375" height="340"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dsB-Xh-jOl">
<rect key="frame" x="0.0" y="0.0" width="375" height="180"/>
<constraints>
<constraint firstAttribute="height" constant="180" id="9eu-ij-E7g"/>
</constraints>
</imageView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jGS-bD-wyb">
<rect key="frame" x="0.0" y="180" width="375" height="159"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="226-gO-8GN">
<rect key="frame" x="16" y="0.0" width="343" height="159"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Z9F-Yb-cDq">
<rect key="frame" x="0.0" y="0.0" width="343" height="48"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Paris" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TFR-Qj-pUF">
<rect key="frame" x="0.0" y="16" width="343" height="32"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="24"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="bold24:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<constraints>
<constraint firstItem="TFR-Qj-pUF" firstAttribute="top" secondItem="Z9F-Yb-cDq" secondAttribute="top" constant="16" id="1Pq-uJ-IXO"/>
<constraint firstAttribute="trailing" secondItem="TFR-Qj-pUF" secondAttribute="trailing" id="9jE-YY-ZE7"/>
<constraint firstItem="TFR-Qj-pUF" firstAttribute="leading" secondItem="Z9F-Yb-cDq" secondAttribute="leading" id="Mqn-yA-iWF"/>
<constraint firstAttribute="bottom" secondItem="TFR-Qj-pUF" secondAttribute="bottom" id="j5w-Th-flQ"/>
</constraints>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" buttonType="system" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="cxc-pt-qei">
<rect key="frame" x="0.0" y="48" width="343" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="g6j-hs-K1V"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
<state key="normal" title="DESCRIPTION"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="FlatNormalTransButton"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="onDescription:" destination="-1" eventType="touchUpInside" id="fQR-bp-Mds"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TIy-PU-SzO">
<rect key="frame" x="0.0" y="88" width="343" height="10"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="HNz-j1-2hp"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bit-FN-ytL">
<rect key="frame" x="0.0" y="98" width="343" height="61"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aEi-QI-Tol">
<rect key="frame" x="0.0" y="0.0" width="343" height="1"/>
<color key="backgroundColor" white="0.0" alpha="0.11852525684931507" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="soU-hC-iDQ"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<constraints>
<constraint firstItem="aEi-QI-Tol" firstAttribute="leading" secondItem="bit-FN-ytL" secondAttribute="leading" id="bGS-V6-zTn"/>
<constraint firstItem="aEi-QI-Tol" firstAttribute="top" secondItem="bit-FN-ytL" secondAttribute="top" id="jX7-lf-0c5"/>
<constraint firstAttribute="trailing" secondItem="aEi-QI-Tol" secondAttribute="trailing" id="xWz-Ie-kOO"/>
</constraints>
</view>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="226-gO-8GN" secondAttribute="trailing" constant="16" id="Bc0-zj-luu"/>
<constraint firstItem="226-gO-8GN" firstAttribute="leading" secondItem="jGS-bD-wyb" secondAttribute="leading" constant="16" id="CBz-2b-FdW"/>
<constraint firstAttribute="bottom" secondItem="226-gO-8GN" secondAttribute="bottom" id="eCj-gO-9cQ"/>
<constraint firstItem="226-gO-8GN" firstAttribute="top" secondItem="jGS-bD-wyb" secondAttribute="top" id="wIb-n9-mts"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="p5d-oU-wVm">
<rect key="frame" x="0.0" y="339" width="375" height="1"/>
<color key="backgroundColor" white="0.0" alpha="0.11852525680000001" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="GqW-4j-7uM"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="KGV-R1-hoS" secondAttribute="bottom" id="Mds-pB-3uh"/>
<constraint firstItem="KGV-R1-hoS" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="bKw-en-SqM"/>
<constraint firstItem="KGV-R1-hoS" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="j8z-W0-Y6S"/>
<constraint firstAttribute="trailing" secondItem="KGV-R1-hoS" secondAttribute="trailing" id="wv4-3Q-Ihc"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<point key="canvasLocation" x="111.2" y="-128.63568215892056"/>
</view>
</objects>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View file

@ -0,0 +1,186 @@
extension BookmarksListSortingType {
init(_ sortingType: BookmarksSortingType) {
switch sortingType {
case .byType:
self = .type
case .byDistance:
self = .distance
case .byTime:
self = .date
case .byName:
self = .name
@unknown default:
fatalError()
}
}
}
final class BookmarksListInteractor: NSObject {
private var markGroupId: MWMMarkGroupID
private var bookmarksManager: BookmarksManager
var onCategoryReload: ((GroupReloadingResult) -> Void)?
init(markGroupId: MWMMarkGroupID) {
self.markGroupId = markGroupId
self.bookmarksManager = BookmarksManager.shared()
super.init()
self.addToBookmarksManagerObserverList()
}
deinit {
removeFromBookmarksManagerObserverList()
}
}
extension BookmarksListInteractor: IBookmarksListInteractor {
func getBookmarkGroup() -> BookmarkGroup {
bookmarksManager.category(withId: markGroupId)
}
func hasDescription() -> Bool {
bookmarksManager.hasExtraInfo(markGroupId)
}
func prepareForSearch() {
bookmarksManager.prepare(forSearch: markGroupId)
}
func search(_ text: String, completion: @escaping ([Bookmark]) -> Void) {
bookmarksManager.searchBookmarksGroup(markGroupId, text: text) {
completion($0)
}
}
func availableSortingTypes(hasMyPosition: Bool) -> [BookmarksListSortingType] {
bookmarksManager.availableSortingTypes(markGroupId, hasMyPosition: hasMyPosition).map {
BookmarksSortingType(rawValue: $0.intValue)!
}.map {
switch $0 {
case .byType:
return BookmarksListSortingType.type
case .byDistance:
return BookmarksListSortingType.distance
case .byTime:
return BookmarksListSortingType.date
case .byName:
return BookmarksListSortingType.name
@unknown default:
fatalError()
}
}
}
func viewOnMap() {
FrameworkHelper.show(onMap: markGroupId)
}
func viewBookmarkOnMap(_ bookmarkId: MWMMarkID) {
FrameworkHelper.showBookmark(bookmarkId)
}
func viewTrackOnMap(_ trackId: MWMTrackID) {
FrameworkHelper.showTrack(trackId)
}
func setGroup(_ groupId: MWMMarkGroupID, visible: Bool) {
bookmarksManager.setCategory(groupId, isVisible: visible)
}
func sort(_ sortingType: BookmarksListSortingType,
location: CLLocation?,
completion: @escaping ([BookmarksSection]) -> Void) {
let coreSortingType: BookmarksSortingType
switch sortingType {
case .distance:
coreSortingType = .byDistance
case .date:
coreSortingType = .byTime
case .type:
coreSortingType = .byType
case .name:
coreSortingType = .byName
}
bookmarksManager.sortBookmarks(markGroupId,
sortingType: coreSortingType,
location: location) { sections in
guard let sections = sections else { return }
completion(sections)
}
}
func resetSort() {
bookmarksManager.resetLastSortingType(markGroupId)
}
func lastSortingType() -> BookmarksListSortingType? {
guard bookmarksManager.hasLastSortingType(markGroupId) else {
return nil
}
return BookmarksListSortingType(bookmarksManager.lastSortingType(markGroupId))
}
func deleteBookmark(_ bookmarkId: MWMMarkID) {
bookmarksManager.deleteBookmark(bookmarkId)
}
func deleteTrack(_ trackId: MWMTrackID) {
bookmarksManager.deleteTrack(trackId)
}
func moveBookmark(_ bookmarkId: MWMMarkID, toGroupId groupId: MWMMarkGroupID) {
bookmarksManager.moveBookmark(bookmarkId, toGroupId: groupId)
}
func moveTrack(_ trackId: MWMTrackID, toGroupId groupId: MWMMarkGroupID) {
bookmarksManager.moveTrack(trackId, toGroupId: groupId)
}
func updateBookmark(_ bookmarkId: MWMMarkID, setGroupId groupId: MWMMarkGroupID, title: String, color: BookmarkColor, description: String) {
bookmarksManager.updateBookmark(bookmarkId, setGroupId: groupId, title: title, color: color, description: description)
}
func updateTrack(_ trackId: MWMTrackID, setGroupId groupId: MWMMarkGroupID) {
bookmarksManager.moveTrack(trackId, toGroupId: groupId)
}
func deleteBookmarksGroup() {
bookmarksManager.deleteCategory(markGroupId)
}
func canDeleteGroup() -> Bool {
bookmarksManager.userCategoriesCount() > 1
}
func exportFile(fileType: KmlFileType, completion: @escaping SharingResultCompletionHandler) {
bookmarksManager.shareCategory(markGroupId, fileType: fileType, completion: completion)
}
func finishExportFile() {
bookmarksManager.finishSharing()
}
func addToBookmarksManagerObserverList() {
bookmarksManager.add(self)
}
func removeFromBookmarksManagerObserverList() {
bookmarksManager.remove(self)
}
func reloadCategory() {
onCategoryReload?(bookmarksManager.hasCategory(markGroupId) ? .success : .notFound)
}
}
// MARK: - BookmarksObserver
extension BookmarksListInteractor: BookmarksObserver {
func onBookmarksLoadFinished() {
reloadCategory()
}
func onBookmarksCategoryDeleted(_ groupId: MWMMarkGroupID) {
reloadCategory()
}
}

View file

@ -0,0 +1,144 @@
import Foundation
enum BookmarksListVisibilityButtonState {
case hidden
case hideAll
case showAll
}
enum BookmarkToolbarButtonSource {
case sort
case more
}
enum GroupReloadingResult {
case success
case notFound
}
protocol IBookmarksListSectionViewModel {
var numberOfItems: Int { get }
var sectionTitle: String { get }
var visibilityButtonState: BookmarksListVisibilityButtonState { get }
var canEdit: Bool { get }
}
protocol IBookmarksSectionViewModel: IBookmarksListSectionViewModel {
var bookmarks: [IBookmarksListItemViewModel] { get }
}
protocol ITracksSectionViewModel: IBookmarksListSectionViewModel {
var tracks: [IBookmarksListItemViewModel] { get }
}
protocol ISubgroupsSectionViewModel: IBookmarksListSectionViewModel {
var subgroups: [ISubgroupViewModel] { get }
var type: BookmarkGroupType { get }
}
protocol IBookmarksListItemViewModel {
var name: String { get }
var subtitle: String { get }
var image: UIImage { get }
var colorDidTapAction: (() -> Void)? { get }
}
protocol ISubgroupViewModel {
var subgroupName: String { get }
var subtitle: String { get }
var isVisible: Bool { get }
}
protocol IBookmarksListMenuItem {
var title: String { get }
var destructive: Bool { get }
var enabled: Bool { get }
var action: () -> Void { get }
}
protocol IBookmarksListView: AnyObject {
func setTitle(_ title: String)
func setInfo(_ info: IBookmarksListInfoViewModel)
func setSections(_ sections: [IBookmarksListSectionViewModel])
func setMoreItemTitle(_ itemTitle: String)
func showMenu(_ items: [IBookmarksListMenuItem], from source: BookmarkToolbarButtonSource)
func showColorPicker(with pickerType: ColorPickerType, _ completion: ((UIColor) -> Void)?)
func enableEditing(_ enable: Bool)
func share(_ url: URL, completion: @escaping () -> Void)
func showError(title: String, message: String)
}
protocol IBookmarksListPresenter {
func viewDidLoad()
func viewDidAppear()
func activateSearch()
func deactivateSearch()
func cancelSearch()
func search(_ text: String)
func sort()
func more()
func deleteItem(in section: IBookmarksListSectionViewModel, at index: Int)
func moveItem(in section: IBookmarksListSectionViewModel, at index: Int)
func editItem(in section: IBookmarksListSectionViewModel, at index: Int)
func selectItem(in section: IBookmarksListSectionViewModel, at index: Int)
func checkItem(in section: IBookmarksListSectionViewModel, at index: Int, checked: Bool)
func toggleVisibility(in section: IBookmarksListSectionViewModel)
func showDescription()
}
enum BookmarksListSortingType {
case distance
case date
case type
case name
}
protocol IBookmarksListInteractor {
var onCategoryReload: ((GroupReloadingResult) -> Void)? { get set }
func getBookmarkGroup() -> BookmarkGroup
func hasDescription() -> Bool
func prepareForSearch()
func search(_ text: String, completion: @escaping ([Bookmark]) -> Void)
func availableSortingTypes(hasMyPosition: Bool) -> [BookmarksListSortingType]
func viewOnMap()
func viewBookmarkOnMap(_ bookmarkId: MWMMarkID)
func viewTrackOnMap(_ trackId: MWMTrackID)
func setGroup(_ groupId: MWMMarkGroupID, visible: Bool)
func sort(_ sortingType: BookmarksListSortingType,
location: CLLocation?,
completion: @escaping ([BookmarksSection]) -> Void)
func resetSort()
func lastSortingType() -> BookmarksListSortingType?
func deleteBookmark(_ bookmarkId: MWMMarkID)
func deleteTrack(_ trackId: MWMTrackID)
func moveBookmark(_ bookmarkId: MWMMarkID, toGroupId: MWMMarkGroupID)
func moveTrack(_ trackId: MWMTrackID, toGroupId: MWMMarkGroupID)
func updateBookmark(_ bookmarkId: MWMMarkID, setGroupId groupId: MWMMarkGroupID, title: String, color: BookmarkColor, description: String)
func updateTrack(_ trackId: MWMTrackID, setGroupId groupId: MWMMarkGroupID)
func deleteBookmarksGroup()
func canDeleteGroup() -> Bool
func exportFile(fileType: KmlFileType, completion: @escaping SharingResultCompletionHandler)
func finishExportFile()
}
protocol IBookmarksListRouter {
func listSettings(_ bookmarkGroup: BookmarkGroup, delegate: CategorySettingsViewControllerDelegate?)
func viewOnMap(_ bookmarkGroup: BookmarkGroup)
func showDescription(_ bookmarkGroup: BookmarkGroup)
func showSubgroup(_ subgroupId: MWMMarkGroupID)
func selectGroup(currentGroupName groupName: String,
currentGroupId groupId: MWMMarkGroupID,
delegate: SelectBookmarkGroupViewControllerDelegate?)
func editBookmark(bookmarkId: MWMMarkID, completion: @escaping (Bool) -> Void)
func editTrack(trackId: MWMTrackID, completion: @escaping (Bool) -> Void)
func goBack()
}
protocol IBookmarksListInfoViewModel {
var title: String { get }
var description: String { get }
var hasDescription: Bool { get }
var isHtmlDescription: Bool { get }
var imageUrl: URL? { get }
}

View file

@ -0,0 +1,562 @@
protocol BookmarksListDelegate: AnyObject {
func bookmarksListDidDeleteGroup()
}
final class BookmarksListPresenter {
private unowned let view: IBookmarksListView
private let router: IBookmarksListRouter
private var interactor: IBookmarksListInteractor
private weak var delegate: BookmarksListDelegate?
private var bookmarkGroup: BookmarkGroup
private enum EditableItem {
case bookmark(MWMMarkID)
case track(MWMTrackID)
}
private var editingItem: EditableItem?
init(view: IBookmarksListView,
router: IBookmarksListRouter,
delegate: BookmarksListDelegate?,
interactor: IBookmarksListInteractor) {
self.view = view
self.router = router
self.delegate = delegate
self.interactor = interactor
self.bookmarkGroup = interactor.getBookmarkGroup()
self.subscribeOnGroupReloading()
}
private func subscribeOnGroupReloading() {
interactor.onCategoryReload = { [weak self] result in
guard let self else { return }
switch result {
case .notFound:
self.router.goBack()
case .success:
self.bookmarkGroup = self.interactor.getBookmarkGroup()
self.reload()
}
}
}
private func reload() {
guard let sortingType = interactor.lastSortingType() else {
setDefaultSections()
return
}
sort(sortingType)
}
private func setDefaultSections() {
interactor.resetSort()
var sections: [IBookmarksListSectionViewModel] = []
let tracks = bookmarkGroup.tracks.map { track in
TrackViewModel(track, formattedDistance: formatDistance(Double(track.trackLengthMeters)), colorDidTap: {
self.view.showColorPicker(with: .defaultColorPicker(track.trackColor)) { color in
BookmarksManager.shared().updateTrack(track.trackId, setColor: color)
self.reload()
}
})
}
if !tracks.isEmpty {
sections.append(TracksSectionViewModel(tracks: tracks))
}
let collections = bookmarkGroup.collections.map { SubgroupViewModel($0) }
if !collections.isEmpty {
sections.append(SubgroupsSectionViewModel(title: L("collections"), subgroups: collections, type: .collection))
}
let categories = bookmarkGroup.categories.map { SubgroupViewModel($0)}
if !categories.isEmpty {
sections.append(SubgroupsSectionViewModel(title: L("categories"), subgroups: categories, type: .category))
}
let bookmarks = mapBookmarks(bookmarkGroup.bookmarks)
if !bookmarks.isEmpty {
sections.append(BookmarksSectionViewModel(title: L("bookmarks"), bookmarks: bookmarks))
}
view.setSections(sections)
}
private func mapBookmarks(_ bookmarks: [Bookmark]) -> [BookmarkViewModel] {
let location = LocationManager.lastLocation()
return bookmarks.map { bookmark in
let formattedDistance: String?
if let location = location {
let distance = location.distance(from: CLLocation(latitude: bookmark.locationCoordinate.latitude,
longitude: bookmark.locationCoordinate.longitude))
formattedDistance = formatDistance(distance)
} else {
formattedDistance = nil
}
return BookmarkViewModel(bookmark, formattedDistance: formattedDistance, colorDidTap: { [weak self] in
self?.view.showColorPicker(with: .bookmarkColorPicker(bookmark.bookmarkColor)) { color in
guard let bookmarkColor = BookmarkColor.bookmarkColor(from: color) else { return }
BookmarksManager.shared().updateBookmark(bookmark.bookmarkId, setColor: bookmarkColor)
self?.reload()
}
})
}
}
private func formatDistance(_ distance: Double) -> String {
DistanceFormatter.distanceString(fromMeters: distance)
}
private func showSortMenu() {
var sortItems = interactor.availableSortingTypes(hasMyPosition: LocationManager.lastLocation() != nil)
.map { sortingType -> BookmarksListMenuItem in
switch sortingType {
case .distance:
return BookmarksListMenuItem(title: L("sort_distance"), action: { [weak self] in
self?.sort(.distance)
})
case .date:
return BookmarksListMenuItem(title: L("sort_date"), action: { [weak self] in
self?.sort(.date)
})
case .type:
return BookmarksListMenuItem(title: L("sort_type"), action: { [weak self] in
self?.sort(.type)
})
case .name:
return BookmarksListMenuItem(title: L("sort_name"), action: { [weak self] in
self?.sort(.name)
})
}
}
sortItems.append(BookmarksListMenuItem(title: L("sort_default"), action: { [weak self] in
self?.setDefaultSections()
}))
view.showMenu(sortItems, from: .sort)
}
private func showMoreMenu() {
var moreItems: [BookmarksListMenuItem] = []
moreItems.append(BookmarksListMenuItem(title: L("search_show_on_map"), action: { [weak self] in
self?.viewOnMap()
}))
moreItems.append(BookmarksListMenuItem(title: L("edit"), action: { [weak self] in
guard let self = self else { return }
self.router.listSettings(self.bookmarkGroup, delegate: self)
}))
func exportMenuItem(for fileType: KmlFileType) -> BookmarksListMenuItem {
let title: String
switch fileType {
case .text:
title = L("export_file")
case .gpx:
title = L("export_file_gpx")
default:
fatalError("Unexpected file type")
}
return BookmarksListMenuItem(title: title, action: { [weak self] in
self?.interactor.exportFile(fileType: fileType) { (status, url) in
switch status {
case .success:
guard let url = url else { fatalError() }
self?.view.share(url) {
self?.interactor.finishExportFile()
}
case .emptyCategory:
self?.view.showError(title: L("bookmarks_error_title_share_empty"),
message: L("bookmarks_error_message_share_empty"))
case .archiveError, .fileError:
self?.view.showError(title: L("dialog_routing_system_error"),
message: L("bookmarks_error_message_share_general"))
}
}
})
}
moreItems.append(exportMenuItem(for: .text))
moreItems.append(exportMenuItem(for: .gpx))
moreItems.append(BookmarksListMenuItem(title: L("delete_list"),
destructive: true,
enabled: interactor.canDeleteGroup(),
action: { [weak self] in
self?.interactor.deleteBookmarksGroup()
self?.delegate?.bookmarksListDidDeleteGroup()
}))
view.showMenu(moreItems, from: .more)
}
private func viewOnMap() {
interactor.viewOnMap()
router.viewOnMap(bookmarkGroup)
}
private func sort(_ sortingType: BookmarksListSortingType) {
interactor.sort(sortingType, location: LocationManager.lastLocation()) { [weak self] sortedSections in
let sections = sortedSections.map { (bookmarksSection) -> IBookmarksListSectionViewModel in
if let bookmarks = bookmarksSection.bookmarks, let self = self {
return BookmarksSectionViewModel(title: bookmarksSection.sectionName, bookmarks: self.mapBookmarks(bookmarks))
}
if let tracks = bookmarksSection.tracks, let self = self {
return TracksSectionViewModel(tracks: tracks.map { track in
TrackViewModel(track, formattedDistance: self.formatDistance(Double(track.trackLengthMeters)), colorDidTap: {
self.view.showColorPicker(with: .defaultColorPicker(track.trackColor)) { color in
BookmarksManager.shared().updateTrack(track.trackId, setColor: color)
self.reload()
}
})
})
}
fatalError()
}
self?.view.setSections(sections)
}
}
}
extension BookmarksListPresenter: IBookmarksListPresenter {
func viewDidLoad() {
reload()
view.setTitle(bookmarkGroup.title)
view.setMoreItemTitle(L("placepage_more_button"))
view.enableEditing(true)
let info = BookmarksListInfo(title: bookmarkGroup.title,
description: bookmarkGroup.detailedAnnotation,
hasDescription: bookmarkGroup.hasDescription,
isHtmlDescription: bookmarkGroup.isHtmlDescription,
imageUrl: bookmarkGroup.imageUrl,
hasLogo: false)
view.setInfo(info)
}
func viewDidAppear() {
reload()
}
func activateSearch() {
interactor.prepareForSearch()
}
func deactivateSearch() {
}
func cancelSearch() {
reload()
}
func search(_ text: String) {
interactor.search(text) { [weak self] in
guard let self = self else { return }
let bookmarks = self.mapBookmarks($0)
self.view.setSections(bookmarks.isEmpty ? [] : [BookmarksSectionViewModel(title: L("bookmarks"),
bookmarks: bookmarks)])
}
}
func more() {
showMoreMenu()
}
func sort() {
showSortMenu()
}
func deleteItem(in section: IBookmarksListSectionViewModel, at index: Int) {
switch section {
case let bookmarksSection as IBookmarksSectionViewModel:
guard let bookmark = bookmarksSection.bookmarks[index] as? BookmarkViewModel else { fatalError() }
interactor.deleteBookmark(bookmark.bookmarkId)
reload()
case let tracksSection as ITracksSectionViewModel:
guard let track = tracksSection.tracks[index] as? TrackViewModel else { fatalError() }
interactor.deleteTrack(track.trackId)
reload()
default:
fatalError("Cannot delete item: unsupported section type: \(section.self)")
}
}
func moveItem(in section: IBookmarksListSectionViewModel, at index: Int) {
let group = interactor.getBookmarkGroup()
switch section {
case let bookmarksSection as IBookmarksSectionViewModel:
guard let bookmark = bookmarksSection.bookmarks[index] as? BookmarkViewModel else { fatalError() }
editingItem = .bookmark(bookmark.bookmarkId)
router.selectGroup(currentGroupName: group.title, currentGroupId: group.categoryId, delegate: self)
case let tracksSection as ITracksSectionViewModel:
guard let track = tracksSection.tracks[index] as? TrackViewModel else { fatalError() }
editingItem = .track(track.trackId)
router.selectGroup(currentGroupName: group.title, currentGroupId: group.categoryId, delegate: self)
default:
fatalError("Cannot move item: unsupported section type: \(section.self)")
}
}
func editItem(in section: IBookmarksListSectionViewModel, at index: Int) {
switch section {
case let bookmarksSection as IBookmarksSectionViewModel:
guard let bookmarkId = (bookmarksSection.bookmarks[index] as? BookmarkViewModel)?.bookmarkId else { fatalError() }
router.editBookmark(bookmarkId: bookmarkId) { [weak self] wasChanged in
if wasChanged { self?.reload() }
}
case let tracksSection as ITracksSectionViewModel:
guard let trackId = (tracksSection.tracks[index] as? TrackViewModel)?.trackId else { fatalError() }
router.editTrack(trackId: trackId) { [weak self] wasChanged in
if wasChanged { self?.reload() }
}
default:
fatalError("Cannot edit item: unsupported section type: \(section.self)")
}
}
func selectItem(in section: IBookmarksListSectionViewModel, at index: Int) {
switch section {
case let bookmarksSection as IBookmarksSectionViewModel:
let bookmark = bookmarksSection.bookmarks[index] as! BookmarkViewModel
interactor.viewBookmarkOnMap(bookmark.bookmarkId)
router.viewOnMap(bookmarkGroup)
case let tracksSection as ITracksSectionViewModel:
let track = tracksSection.tracks[index] as! TrackViewModel
interactor.viewTrackOnMap(track.trackId)
router.viewOnMap(bookmarkGroup)
case let subgroupsSection as ISubgroupsSectionViewModel:
let subgroup = subgroupsSection.subgroups[index] as! SubgroupViewModel
router.showSubgroup(subgroup.groupId)
if subgroup.type == .collection {
} else if subgroup.type == .category {
} else {
assertionFailure()
}
default:
fatalError("Wrong section type: \(section.self)")
}
}
func showDescription() {
router.showDescription(bookmarkGroup)
}
func checkItem(in section: IBookmarksListSectionViewModel, at index: Int, checked: Bool) {
switch section {
case let subgroupsSection as ISubgroupsSectionViewModel:
let subgroup = subgroupsSection.subgroups[index] as! SubgroupViewModel
interactor.setGroup(subgroup.groupId, visible: checked)
reload()
default:
fatalError("Wrong section type: \(section.self)")
}
}
func toggleVisibility(in section: IBookmarksListSectionViewModel) {
switch section {
case let subgroupsSection as ISubgroupsSectionViewModel:
let visible: Bool
switch subgroupsSection.visibilityButtonState {
case .hidden:
fatalError("Unexpected visibility button state")
case .hideAll:
visible = false
case .showAll:
visible = true
}
subgroupsSection.subgroups.forEach {
let subgroup = $0 as! SubgroupViewModel
interactor.setGroup(subgroup.groupId, visible: visible)
}
reload()
default:
fatalError("Wrong section type: \(section.self)")
}
}
}
extension BookmarksListPresenter: CategorySettingsViewControllerDelegate {
func categorySettingsController(_ viewController: CategorySettingsViewController, didEndEditing categoryId: MWMMarkGroupID) {
let info = BookmarksListInfo(title: bookmarkGroup.title,
description: bookmarkGroup.detailedAnnotation,
hasDescription: bookmarkGroup.hasDescription,
isHtmlDescription: bookmarkGroup.isHtmlDescription,
imageUrl: bookmarkGroup.imageUrl,
hasLogo: false)
view.setInfo(info)
viewController.goBack()
}
func categorySettingsController(_ viewController: CategorySettingsViewController, didDelete categoryId: MWMMarkGroupID) {
if let delegate = delegate as? UIViewController {
viewController.navigationController?.popToViewController(delegate, animated: true)
}
}
}
extension BookmarksListPresenter: SelectBookmarkGroupViewControllerDelegate {
func bookmarkGroupViewController(_ viewController: SelectBookmarkGroupViewController,
didSelect groupTitle: String,
groupId: MWMMarkGroupID) {
defer { viewController.dismiss(animated: true) }
guard groupId != bookmarkGroup.categoryId else { return }
switch editingItem {
case .bookmark(let bookmarkId):
interactor.moveBookmark(bookmarkId, toGroupId: groupId)
case .track(let trackId):
interactor.moveTrack(trackId, toGroupId: groupId)
case .none:
break
}
editingItem = nil
if bookmarkGroup.bookmarksCount > 0 || bookmarkGroup.trackCount > 0 {
reload()
} else {
// if there are no bookmarks or tracks in current group no need to show this group
// e.g. popping view controller 2 times
if let rootNavigationController = viewController.presentingViewController as? UINavigationController {
rootNavigationController.popViewController(animated: false)
}
}
}
}
extension IBookmarksSectionViewModel {
var numberOfItems: Int { bookmarks.count }
var visibilityButtonState: BookmarksListVisibilityButtonState { .hidden }
var canEdit: Bool { true }
}
extension ITracksSectionViewModel {
var numberOfItems: Int { tracks.count }
var sectionTitle: String { L("tracks_title") }
var visibilityButtonState: BookmarksListVisibilityButtonState { .hidden }
var canEdit: Bool { true }
}
extension ISubgroupsSectionViewModel {
var numberOfItems: Int { subgroups.count }
var visibilityButtonState: BookmarksListVisibilityButtonState {
subgroups.reduce(false) { $0 ? $0 : $1.isVisible } ? .hideAll : .showAll
}
var canEdit: Bool { false }
}
fileprivate struct BookmarkViewModel: IBookmarksListItemViewModel {
let bookmarkId: MWMMarkID
let name: String
let subtitle: String
var image: UIImage {
bookmarkColor.image(bookmarkIconName)
}
var colorDidTapAction: (() -> Void)?
private let bookmarkColor: BookmarkColor
private let bookmarkIconName: String
init(_ bookmark: Bookmark, formattedDistance: String?, colorDidTap: (() -> Void)?) {
bookmarkId = bookmark.bookmarkId
name = bookmark.bookmarkName
bookmarkColor = bookmark.bookmarkColor
bookmarkIconName = bookmark.bookmarkIconName
subtitle = [formattedDistance, bookmark.bookmarkType].compactMap { $0 }.joined(separator: "")
colorDidTapAction = colorDidTap
}
}
fileprivate struct TrackViewModel: IBookmarksListItemViewModel {
let trackId: MWMTrackID
let name: String
let subtitle: String
var image: UIImage {
circleImageForColor(trackColor, frameSize: 22)
}
var colorDidTapAction: (() -> Void)?
private let trackColor: UIColor
init(_ track: Track, formattedDistance: String, colorDidTap: (() -> Void)?) {
trackId = track.trackId
name = track.trackName
subtitle = "\(L("length")) \(formattedDistance)"
trackColor = track.trackColor
colorDidTapAction = colorDidTap
}
}
fileprivate struct SubgroupViewModel: ISubgroupViewModel {
let groupId: MWMMarkGroupID
let subgroupName: String
let subtitle: String
let isVisible: Bool
let type: BookmarkGroupType
init(_ bookmarkGroup: BookmarkGroup) {
groupId = bookmarkGroup.categoryId
subgroupName = bookmarkGroup.title
subtitle = bookmarkGroup.placesCountTitle()
isVisible = bookmarkGroup.isVisible
type = bookmarkGroup.type
}
}
fileprivate struct BookmarksSectionViewModel: IBookmarksSectionViewModel {
let sectionTitle: String
let bookmarks: [IBookmarksListItemViewModel]
init(title: String, bookmarks: [IBookmarksListItemViewModel]) {
sectionTitle = title
self.bookmarks = bookmarks
}
}
fileprivate struct TracksSectionViewModel: ITracksSectionViewModel {
let tracks: [IBookmarksListItemViewModel]
init(tracks: [IBookmarksListItemViewModel]) {
self.tracks = tracks
}
}
fileprivate struct SubgroupsSectionViewModel: ISubgroupsSectionViewModel {
let subgroups: [ISubgroupViewModel]
let sectionTitle: String
var type: BookmarkGroupType
init(title: String, subgroups: [ISubgroupViewModel], type: BookmarkGroupType) {
sectionTitle = title
self.type = type
self.subgroups = subgroups
}
}
fileprivate struct BookmarksListMenuItem: IBookmarksListMenuItem {
let title: String
let destructive: Bool
let enabled: Bool
let action: () -> Void
init(title: String, destructive: Bool = false, enabled: Bool = true, action: @escaping () -> Void) {
self.title = title
self.destructive = destructive
self.enabled = enabled
self.action = action
}
}
fileprivate struct BookmarksListInfo: IBookmarksListInfoViewModel {
let title: String
let description: String
let hasDescription: Bool
let isHtmlDescription: Bool
let imageUrl: URL?
let hasLogo: Bool
init(title: String, description: String, hasDescription: Bool, isHtmlDescription: Bool, imageUrl: URL? = nil, hasLogo: Bool = false) {
self.title = title
self.description = description
self.hasDescription = hasDescription
self.isHtmlDescription = isHtmlDescription
self.imageUrl = imageUrl
self.hasLogo = hasLogo
}
}

View file

@ -0,0 +1,86 @@
final class BookmarksListRouter {
private let mapViewController: MapViewController
private weak var coordinator: BookmarksCoordinator?
init(_ mapViewController: MapViewController, bookmarksCoordinator: BookmarksCoordinator?) {
self.mapViewController = mapViewController
self.coordinator = bookmarksCoordinator
}
}
extension BookmarksListRouter: IBookmarksListRouter {
func listSettings(_ bookmarkGroup: BookmarkGroup, delegate: CategorySettingsViewControllerDelegate?) {
let listSettingsController = CategorySettingsViewController(bookmarkGroup: bookmarkGroup)
listSettingsController.delegate = delegate
mapViewController.navigationController?.pushViewController(listSettingsController, animated: true)
}
func viewOnMap(_ bookmarkGroup: BookmarkGroup) {
coordinator?.hide(categoryId: bookmarkGroup.categoryId)
}
func showDescription(_ bookmarkGroup: BookmarkGroup) {
let description = BookmarksListRouter.prepareHtmlDescription(bookmarkGroup)
let descriptionViewController = WebViewController(html: description, baseUrl: nil, title: bookmarkGroup.title)!
descriptionViewController.openInSafari = true
mapViewController.navigationController?.pushViewController(descriptionViewController, animated: true)
}
private static func prepareHtmlDescription(_ bookmarkGroup: BookmarkGroup) -> String {
var description = bookmarkGroup.detailedAnnotation
if bookmarkGroup.isHtmlDescription {
if !description.contains("<body>") {
description = "<body>" + description
}
} else {
description = description.replacingOccurrences(of: "\n", with: "<br>")
let header = """
<head>
<style>
body {
line-height: 1.4;
margin-right: 16px;
margin-left: 16px;
}
</style>
</head>
<body>
"""
description = header + description
}
if !description.contains("</body>") {
description += "</body>"
}
return description
}
func showSubgroup(_ subgroupId: MWMMarkGroupID) {
let bookmarksListViewController = BookmarksListBuilder.build(markGroupId: subgroupId,
bookmarksCoordinator: coordinator)
mapViewController.navigationController?.pushViewController(bookmarksListViewController, animated: true)
}
func selectGroup(currentGroupName groupName: String,
currentGroupId groupId: MWMMarkGroupID,
delegate: SelectBookmarkGroupViewControllerDelegate?) {
let groupViewController = SelectBookmarkGroupViewController(groupName: groupName, groupId: groupId)
groupViewController.delegate = delegate
let navigationController = UINavigationController(rootViewController: groupViewController)
mapViewController.present(navigationController, animated: true, completion: nil)
}
func editBookmark(bookmarkId: MWMMarkID, completion: @escaping (Bool) -> Void) {
let editBookmarkController = UIStoryboard.instance(.main).instantiateViewController(withIdentifier: "MWMEditBookmarkController") as! EditBookmarkViewController
editBookmarkController.configure(with: bookmarkId, editCompletion: completion)
mapViewController.navigationController?.pushViewController(editBookmarkController, animated: true)
}
func editTrack(trackId: MWMTrackID, completion: @escaping (Bool) -> Void) {
let editTrackController = EditTrackViewController(trackId: trackId, editCompletion: completion)
mapViewController.navigationController?.pushViewController(editTrackController, animated: true)
}
func goBack() {
coordinator?.goBack()
}
}

View file

@ -0,0 +1,272 @@
final class BookmarksListViewController: MWMViewController {
var presenter: IBookmarksListPresenter!
private var sections: [IBookmarksListSectionViewModel]?
private let cellStrategy = BookmarksListCellStrategy()
private var canEdit = false
@IBOutlet private var tableView: UITableView!
@IBOutlet private var toolBar: UIToolbar!
@IBOutlet private var sortToolbarItem: UIBarButtonItem!
@IBOutlet private var moreToolbarItem: UIBarButtonItem!
private let searchController = UISearchController(searchResultsController: nil)
private lazy var infoViewController: BookmarksListInfoViewController = {
let infoViewController = BookmarksListInfoViewController()
infoViewController.delegate = self
addChild(infoViewController)
tableView.tableHeaderView = infoViewController.view
infoViewController.didMove(toParent: self)
return infoViewController
}()
override func viewDidLoad() {
super.viewDidLoad()
let toolbarItemAttributes = [NSAttributedString.Key.font: UIFont.medium16(),
NSAttributedString.Key.foregroundColor: UIColor.linkBlue()]
sortToolbarItem.setTitleTextAttributes(toolbarItemAttributes, for: .normal)
moreToolbarItem.setTitleTextAttributes(toolbarItemAttributes, for: .normal)
sortToolbarItem.title = L("sort")
extendedLayoutIncludesOpaqueBars = true
searchController.searchBar.placeholder = L("search_in_the_list")
searchController.obscuresBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = alternativeSizeClass(iPhone: true, iPad: false)
searchController.searchBar.delegate = self
searchController.searchBar.applyTheme()
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
cellStrategy.registerCells(tableView)
cellStrategy.cellCheckHandler = { [weak self] (viewModel, index, checked) in
self?.presenter.checkItem(in: viewModel, at: index, checked: checked)
}
cellStrategy.cellVisibilityHandler = { [weak self] viewModel in
self?.presenter.toggleVisibility(in: viewModel)
}
presenter.viewDidLoad()
MWMKeyboard.add(self);
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presenter.viewDidAppear()
}
deinit {
MWMKeyboard.remove(self);
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updateInfoSize()
}
private func updateInfoSize() {
guard let infoView = infoViewController.view else { return }
let infoViewSize = infoView.systemLayoutSizeFitting(CGSize(width: view.width, height: 0),
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel)
infoView.size = infoViewSize
tableView.tableHeaderView = infoView
}
@IBAction private func onSortItem(_ sender: UIBarButtonItem) {
presenter.sort()
}
@IBAction private func onMoreItem(_ sender: UIBarButtonItem) {
presenter.more()
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
tableView.setEditing(editing, animated: animated)
}
}
extension BookmarksListViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
sections?.count ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let section = sections?[section] else { fatalError() }
return section.numberOfItems
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let section = sections?[indexPath.section] else { fatalError() }
return cellStrategy.tableCell(tableView, for: section, at: indexPath)
}
}
extension BookmarksListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
60
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let section = sections?[section] else { fatalError() }
return cellStrategy.headerView(tableView, for: section)
}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
return indexPath
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard let section = sections?[indexPath.section] else { fatalError() }
presenter.selectItem(in: section, at: indexPath.row)
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
guard let section = sections?[indexPath.section] else { fatalError() }
return canEdit && section.canEdit
}
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
isEditing = true
}
func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
isEditing = false
}
func tableView(_ tableView: UITableView,
leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let moveAction = UIContextualAction(style: .normal, title: L("move")) { [weak self] (_, _, completion) in
guard let section = self?.sections?[indexPath.section] else { fatalError() }
self?.presenter.moveItem(in: section, at: indexPath.row)
completion(true)
}
return UISwipeActionsConfiguration(actions: [moveAction])
}
func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: L("delete")) { [weak self] (_, _, completion) in
guard let section = self?.sections?[indexPath.section] else { fatalError() }
self?.presenter.deleteItem(in: section, at: indexPath.row)
completion(true)
}
let editAction = UIContextualAction(style: .normal, title: L("edit")) { [weak self] (_, _, completion) in
guard let section = self?.sections?[indexPath.section] else { fatalError() }
self?.presenter.editItem(in: section, at: indexPath.row)
completion(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction, editAction])
}
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
guard let section = sections?[indexPath.section] else { fatalError() }
presenter.editItem(in: section, at: indexPath.row)
}
}
extension BookmarksListViewController: UISearchBarDelegate {
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBar.setShowsCancelButton(true, animated: true)
presenter.activateSearch()
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchBar.setShowsCancelButton(false, animated: true)
presenter.deactivateSearch()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = nil
searchBar.resignFirstResponder()
presenter.cancelSearch()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard !searchText.isEmpty else {
presenter.cancelSearch()
return
}
presenter.search(searchText)
}
}
extension BookmarksListViewController: IBookmarksListView {
func setTitle(_ title: String) {
self.title = title
}
func setInfo(_ info: IBookmarksListInfoViewModel) {
infoViewController.info = info
updateInfoSize()
}
func setSections(_ sections: [IBookmarksListSectionViewModel]) {
self.sections = sections
tableView.reloadData()
}
func setMoreItemTitle(_ itemTitle: String) {
moreToolbarItem.title = itemTitle
}
func showMenu(_ items: [IBookmarksListMenuItem], from source: BookmarkToolbarButtonSource) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
items.forEach { item in
let action = UIAlertAction(title: item.title, style: item.destructive ? .destructive : .default) { _ in
item.action()
}
action.isEnabled = item.enabled
actionSheet.addAction(action)
}
actionSheet.addAction(UIAlertAction(title: L("cancel"), style: .cancel, handler: nil))
let barButtonItem = switch source {
case .sort: sortToolbarItem
case .more: moreToolbarItem
}
actionSheet.popoverPresentationController?.barButtonItem = barButtonItem
present(actionSheet, animated: true)
}
func showColorPicker(with pickerType: ColorPickerType, _ completionHandler: ((UIColor) -> Void)?) {
ColorPicker.shared.present(from: self, pickerType: pickerType, completionHandler: completionHandler)
}
func enableEditing(_ enable: Bool) {
canEdit = enable
navigationItem.rightBarButtonItem = enable ? editButtonItem : nil
}
func share(_ url: URL, completion: @escaping () -> Void) {
let shareController = ActivityViewController.share(for: url,
message: L("share_bookmarks_email_body")) { (_, _, _, _) in
completion()
}
shareController.present(inParentViewController: self, anchorView: self.toolBar)
}
func showError(title: String, message: String) {
MWMAlertViewController.activeAlert().presentInfoAlert(title, text: message)
}
}
extension BookmarksListViewController: BookmarksListInfoViewControllerDelegate {
func didPressDescription() {
presenter.showDescription()
}
func didUpdateContent() {
updateInfoSize()
}
}
extension BookmarksListViewController: MWMKeyboardObserver {
func onKeyboardAnimation() {
let keyboardHeight = MWMKeyboard.keyboardHeight();
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
}
}

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="BookmarksListViewController" customModule="CoMaps" customModuleProvider="target">
<connections>
<outlet property="moreToolbarItem" destination="Hhy-7w-Mz0" id="0bI-d2-WuP"/>
<outlet property="sortToolbarItem" destination="BWR-ft-be3" id="iiS-BA-nqF"/>
<outlet property="tableView" destination="fva-qQ-WqU" id="XoT-Z8-nGb"/>
<outlet property="toolBar" destination="cD8-kh-Gmp" id="1M2-EB-fbH"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="60" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="fva-qQ-WqU">
<rect key="frame" x="0.0" y="56" width="375" height="567"/>
<color key="backgroundColor" systemColor="groupTableViewBackgroundColor"/>
<inset key="separatorInset" minX="56" minY="0.0" maxX="0.0" maxY="0.0"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="TableView:PressBackground"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="dataSource" destination="-1" id="1RG-NX-TL8"/>
<outlet property="delegate" destination="-1" id="Djq-Nz-win"/>
</connections>
</tableView>
<toolbar opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cD8-kh-Gmp">
<rect key="frame" x="0.0" y="623" width="375" height="44"/>
<items>
<barButtonItem width="8" style="plain" systemItem="fixedSpace" id="E0V-Le-AEM"/>
<barButtonItem title="Sort" style="plain" id="BWR-ft-be3">
<connections>
<action selector="onSortItem:" destination="-1" id="bcl-jw-IY1"/>
</connections>
</barButtonItem>
<barButtonItem style="plain" systemItem="flexibleSpace" id="z0r-ms-Z5o"/>
<barButtonItem title="More..." style="plain" id="Hhy-7w-Mz0">
<connections>
<action selector="onMoreItem:" destination="-1" id="P0K-AE-UgC"/>
</connections>
</barButtonItem>
<barButtonItem width="8" style="plain" systemItem="fixedSpace" id="9Q2-6r-Qjv"/>
</items>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
</toolbar>
</subviews>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="cD8-kh-Gmp" secondAttribute="bottom" id="FgV-RT-cCW"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="cD8-kh-Gmp" secondAttribute="trailing" id="LZs-Au-R4I"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="fva-qQ-WqU" secondAttribute="trailing" id="OsX-Pn-Js4"/>
<constraint firstItem="fva-qQ-WqU" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="d61-I4-Ku3"/>
<constraint firstItem="cD8-kh-Gmp" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="jVJ-nY-UBF"/>
<constraint firstItem="cD8-kh-Gmp" firstAttribute="top" secondItem="fva-qQ-WqU" secondAttribute="bottom" id="lQL-J0-O69"/>
<constraint firstItem="fva-qQ-WqU" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="yZs-bO-F39"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="PressBackground"/>
</userDefinedRuntimeAttributes>
<point key="canvasLocation" x="137.68115942028987" y="95.758928571428569"/>
</view>
</objects>
<resources>
<systemColor name="groupTableViewBackgroundColor">
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

View file

@ -0,0 +1,40 @@
final class BookmarksListCell: UITableViewCell {
private static let extendedImageViewTappableMargin: CGFloat = -15
private var trackColorDidTapAction: (() -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
setupCell()
}
private func setupCell() {
accessoryType = .detailButton
if let imageView {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(colorDidTapAction(_:)))
imageView.addGestureRecognizer(tapGesture)
imageView.isUserInteractionEnabled = true
}
}
func config(_ bookmark: IBookmarksListItemViewModel) {
imageView?.image = bookmark.image
textLabel?.text = bookmark.name
detailTextLabel?.text = bookmark.subtitle
trackColorDidTapAction = bookmark.colorDidTapAction
}
@objc private func colorDidTapAction(_ sender: UITapGestureRecognizer) {
trackColorDidTapAction?()
}
// Extends the imageView tappable area.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let imageView, imageView.convert(imageView.bounds, to: self).insetBy(dx: Self.extendedImageViewTappableMargin, dy: Self.extendedImageViewTappableMargin).contains(point) {
return imageView
} else {
return super.hitTest(point, with: event)
}
}
}

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="QgU-jK-sWK" detailTextLabel="zC9-YD-CfS" rowHeight="64" style="IBUITableViewCellStyleSubtitle" id="KGk-i7-Jjw" customClass="BookmarksListCell" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="64"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QgU-jK-sWK">
<rect key="frame" x="16" y="16" width="25" height="14.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular16:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="zC9-YD-CfS">
<rect key="frame" x="16" y="32.5" width="44" height="14.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<point key="canvasLocation" x="137.68115942028987" y="104.12946428571428"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,58 @@
final class BookmarksListCellStrategy {
private enum CellId {
static let listItem = "BookmarksListCell"
static let subgroup = "SubgroupCell"
static let sectionHeader = "SectionHeader"
}
typealias CheckHandlerClosure = (IBookmarksListSectionViewModel, Int, Bool) -> Void
var cellCheckHandler: CheckHandlerClosure?
typealias VisibilityHandlerClosure = (IBookmarksListSectionViewModel) -> Void
var cellVisibilityHandler: VisibilityHandlerClosure?
func registerCells(_ tableView: UITableView) {
tableView.register(UINib(nibName: "BookmarksListCell", bundle: nil), forCellReuseIdentifier: CellId.listItem)
tableView.register(UINib(nibName: "SubgroupCell", bundle: nil), forCellReuseIdentifier: CellId.subgroup)
tableView.register(UINib(nibName: "BookmarksListSectionHeader", bundle: nil),
forHeaderFooterViewReuseIdentifier: CellId.sectionHeader)
}
func tableCell(_ tableView: UITableView,
for viewModel: IBookmarksListSectionViewModel,
at indexPath: IndexPath) -> UITableViewCell {
switch viewModel {
case let bookmarksSection as IBookmarksSectionViewModel:
let bookmark = bookmarksSection.bookmarks[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: CellId.listItem, for: indexPath) as! BookmarksListCell
cell.config(bookmark)
return cell
case let tracksSection as ITracksSectionViewModel:
let track = tracksSection.tracks[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: CellId.listItem, for: indexPath) as! BookmarksListCell
cell.config(track)
return cell
case let subgroupsSection as ISubgroupsSectionViewModel:
let subgroup = subgroupsSection.subgroups[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: CellId.subgroup, for: indexPath) as! SubgroupCell
cell.config(subgroup)
cell.checkHandler = { [weak self] checked in
self?.cellCheckHandler?(viewModel, indexPath.row, checked)
}
return cell
default:
fatalError("Unexpected item")
}
}
func headerView(_ tableView: UITableView,
for viewModel: IBookmarksListSectionViewModel) -> UITableViewHeaderFooterView {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: CellId.sectionHeader)
as! BookmarksListSectionHeader
headerView.config(viewModel)
headerView.visibilityHandler = { [weak self] in
self?.cellVisibilityHandler?(viewModel)
}
return headerView
}
}

View file

@ -0,0 +1,25 @@
final class BookmarksListSectionHeader: UITableViewHeaderFooterView {
@IBOutlet private var titleLabel: UILabel!
@IBOutlet private var visibilityButton: UIButton!
typealias VisibilityHandlerClosure = () -> Void
var visibilityHandler: VisibilityHandlerClosure?
@IBAction private func onVisibilityButton(_ sender: UIButton) {
visibilityHandler?()
}
func config(_ section: IBookmarksListSectionViewModel) {
titleLabel.text = section.sectionTitle
switch section.visibilityButtonState {
case .hidden:
visibilityButton.isHidden = true
case .hideAll:
visibilityButton.isHidden = false
visibilityButton.setTitle(L("bookmark_lists_hide_all"), for: .normal)
case .showAll:
visibilityButton.isHidden = false
visibilityButton.setTitle(L("bookmark_lists_show_all"), for: .normal)
}
}
}

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="iN0-l3-epB" customClass="BookmarksListSectionHeader" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xCm-Dh-8mT">
<rect key="frame" x="16" y="12" width="343" height="48"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Bookmarks" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yNv-Yr-Hkr">
<rect key="frame" x="0.0" y="0.0" width="287" height="48"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="bold20:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" horizontalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="zdf-Q0-3xO">
<rect key="frame" x="287" y="0.0" width="56" height="48"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<state key="normal" title="Hide All"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="onVisibilityButton:" destination="iN0-l3-epB" eventType="touchUpInside" id="w2u-1a-M9u"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="xCm-Dh-8mT" secondAttribute="bottom" id="3MG-Ep-gMo"/>
<constraint firstItem="xCm-Dh-8mT" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="16" id="9XD-d8-7vk"/>
<constraint firstAttribute="trailing" secondItem="xCm-Dh-8mT" secondAttribute="trailing" constant="16" id="OGF-TS-kyi"/>
<constraint firstItem="xCm-Dh-8mT" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="12" id="OLp-AI-xo9"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="titleLabel" destination="yNv-Yr-Hkr" id="2D1-wr-wFj"/>
<outlet property="visibilityButton" destination="zdf-Q0-3xO" id="Z64-hc-gnc"/>
</connections>
<point key="canvasLocation" x="138.40000000000001" y="201.94902548725639"/>
</view>
</objects>
</document>

View file

@ -0,0 +1,18 @@
final class SubgroupCell: UITableViewCell {
@IBOutlet private var subgroupTitleLabel: UILabel!
@IBOutlet private var subgroupSubtitleLabel: UILabel!
@IBOutlet private var subgroupVisibleMark: Checkmark!
typealias CheckHandlerClosure = (Bool) -> Void
var checkHandler: CheckHandlerClosure?
func config(_ subgroup: ISubgroupViewModel) {
subgroupTitleLabel.text = subgroup.subgroupName
subgroupSubtitleLabel.text = subgroup.subtitle
subgroupVisibleMark.isChecked = subgroup.isVisible
}
@IBAction private func onCheck(_ sender: Checkmark) {
checkHandler?(sender.isChecked)
}
}

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="64" id="KGk-i7-Jjw" customClass="SubgroupCell" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="64"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" horizontalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="mAZ-hb-J9T" customClass="Checkmark" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="56" height="64"/>
<constraints>
<constraint firstAttribute="width" constant="56" id="aar-GM-DaJ"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="image" keyPath="offImage" value="ic_eye_off"/>
<userDefinedRuntimeAttribute type="image" keyPath="onImage" value="ic_eye_on"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="onCheck:" destination="KGk-i7-Jjw" eventType="valueChanged" id="xp2-kL-Hgu"/>
</connections>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="L2V-JY-eeP">
<rect key="frame" x="56" y="11" width="224" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular16:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mcK-1H-ZBS">
<rect key="frame" x="56" y="35" width="224" height="16.5"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_disclosure" translatesAutoresizingMaskIntoConstraints="NO" id="UOn-k5-sEA">
<rect key="frame" x="288" y="20" width="24" height="24"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MWMGray"/>
</userDefinedRuntimeAttributes>
</imageView>
</subviews>
<constraints>
<constraint firstItem="L2V-JY-eeP" firstAttribute="leading" secondItem="mAZ-hb-J9T" secondAttribute="trailing" id="3fE-vM-7lQ"/>
<constraint firstAttribute="bottom" secondItem="mcK-1H-ZBS" secondAttribute="bottom" constant="12.5" id="Ca4-dk-CyT"/>
<constraint firstAttribute="bottom" secondItem="mAZ-hb-J9T" secondAttribute="bottom" id="EEL-tz-cXw"/>
<constraint firstItem="UOn-k5-sEA" firstAttribute="leading" secondItem="mcK-1H-ZBS" secondAttribute="trailing" constant="8" id="GMX-F4-EWk"/>
<constraint firstAttribute="trailing" secondItem="UOn-k5-sEA" secondAttribute="trailing" constant="8" id="Pdg-cN-1Py"/>
<constraint firstItem="mAZ-hb-J9T" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="SzN-rJ-pMf"/>
<constraint firstItem="L2V-JY-eeP" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="11" id="ayo-1v-WC2"/>
<constraint firstItem="mcK-1H-ZBS" firstAttribute="leading" secondItem="mAZ-hb-J9T" secondAttribute="trailing" id="bsr-4x-bfr"/>
<constraint firstItem="mAZ-hb-J9T" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="gws-UG-2oB"/>
<constraint firstItem="UOn-k5-sEA" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="hTX-sY-kSJ"/>
<constraint firstItem="UOn-k5-sEA" firstAttribute="leading" secondItem="L2V-JY-eeP" secondAttribute="trailing" constant="8" id="pua-Zn-zVM"/>
<constraint firstItem="mcK-1H-ZBS" firstAttribute="top" secondItem="L2V-JY-eeP" secondAttribute="bottom" constant="4" id="ycS-hQ-Y91"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<connections>
<outlet property="subgroupSubtitleLabel" destination="mcK-1H-ZBS" id="x96-KI-R0D"/>
<outlet property="subgroupTitleLabel" destination="L2V-JY-eeP" id="ZUX-Yy-zHY"/>
<outlet property="subgroupVisibleMark" destination="mAZ-hb-J9T" id="zrV-cz-C1i"/>
</connections>
<point key="canvasLocation" x="127.53623188405798" y="60.267857142857139"/>
</tableViewCell>
</objects>
<resources>
<image name="ic_disclosure" width="24" height="24"/>
<image name="ic_eye_off" width="24" height="24"/>
<image name="ic_eye_on" width="24" height="24"/>
</resources>
</document>