Rename repo and app

This commit is contained in:
Christoph Hagen
2022-04-28 15:54:13 +02:00
parent 1636932805
commit c119885743
78 changed files with 91 additions and 34 deletions

View File

@@ -0,0 +1,39 @@
//
// CapCell.swift
// CapFinder
//
// Created by User on 22.04.18.
// Copyright © 2018 User. All rights reserved.
//
import UIKit
class CapCell: UITableViewCell {
@IBOutlet private weak var capImage: RoundedImageView!
@IBOutlet private weak var matchLabel: UILabel!
@IBOutlet private weak var nameLabel: UILabel!
@IBOutlet private weak var countLabel: UILabel!
var id: Int = 0
func set(image: UIImage?) {
capImage.image = image ?? UIImage(named: "launch")
}
func set(name: String) {
self.nameLabel.text = name
}
func set(matchLabel: String) {
self.matchLabel.text = matchLabel
}
func set(countLabel: String) {
self.countLabel.text = countLabel
}
}

View File

@@ -0,0 +1,360 @@
//
// GridViewController.swift
// CapCollector
//
// Created by Christoph on 07.01.19.
// Copyright © 2019 CH. All rights reserved.
//
import UIKit
class GridViewController: UIViewController {
/// The number of horizontal pixels for each cap.
static let len: CGFloat = 60
private lazy var rowHeight = GridViewController.len * 0.866
private lazy var margin = GridViewController.len - rowHeight
private var myView: UIView!
private var canvasSize: CGSize = .zero
@IBOutlet weak var scrollView: UIScrollView!
/// A dictionary of the caps for the tiles
private var tiles = [Int]()
/// The name of the tile image
private var name: String = "default"
/// The number of caps horizontally.
private var columns = 40
/// A dictionary for the colors of the caps
private var colors = [Int : UIColor]()
/// The currently displaxed image views indexed by their tile ids
private var installedTiles = [Int : RoundedImageView]()
private var selectedTile: Int? = nil
private weak var selectionView: RoundedButton!
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .portrait
}
override var shouldAutorotate: Bool {
return true
}
private var isShowingColors = false
@IBAction func toggleAverageColor(_ sender: Any) {
isShowingColors = !isShowingColors
for (tile, view) in installedTiles {
if isShowingColors {
view.image = nil
view.backgroundColor = tileColor(tile: tile)
} else {
let id = tiles[tile]
if let image = app.database.storage.thumbnail(for: id) {
view.image = image
continue
}
self.downloadImage(cap: id, tile: tile)
}
}
}
func load(tileImage: Database.TileImage) {
let totalCount = app.database.capCount
let firstNewId = tileImage.caps.count + 1
if totalCount >= firstNewId {
self.tiles = tileImage.caps + (firstNewId...totalCount)
} else {
self.tiles = tileImage.caps
}
self.columns = tileImage.width
self.name = tileImage.name
}
private func saveTileImage() {
let tileImage = Database.TileImage(name: name, width: columns, caps: tiles)
guard app.database.save(tileImage: tileImage) else {
log("Failed to save tile image")
return
}
log("Tile image saved")
}
override func viewDidLoad() {
super.viewDidLoad()
colors = app.database.colors
let width = CGFloat(columns) * GridViewController.len + GridViewController.len / 2
let height = (CGFloat(tiles.count) / CGFloat(columns)).rounded(.up) * rowHeight + margin
canvasSize = CGSize(width: width, height: height)
myView = UIView(frame: CGRect(origin: .zero, size: canvasSize))
scrollView.addSubview(myView)
scrollView.contentSize = canvasSize
scrollView.delegate = self
scrollView.zoomScale = 0.5
scrollView.maximumZoomScale = 1
setZoomRange()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateTiles()
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
myView.addGestureRecognizer(tapRecognizer)
}
override func viewDidLayoutSubviews() {
setZoomRange()
updateTiles()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
saveTileImage()
}
// MARK: Tiles
private func tileColor(tile: Int) -> UIColor? {
let id = tiles[tile]
return colors[id]
}
/**
Switch two tiles.
*/
private func switchTiles(_ lhs: Int, _ rhs: Int) -> Bool {
let temp = tiles[rhs]
tiles[rhs] = tiles[lhs]
tiles[lhs] = temp
return true
}
private func setZoomRange() {
let size = scrollView.frame.size
let a = size.width / canvasSize.width
let b = size.height / canvasSize.height
let scale = min(a,b)
scrollView.minimumZoomScale = min(a,b)
if scrollView.zoomScale < scale {
scrollView.setZoomScale(scale, animated: true)
}
}
@objc func handleTap(_ sender: UITapGestureRecognizer) {
let loc = sender.location(in: myView)
let y = loc.y
let s = y.truncatingRemainder(dividingBy: rowHeight)
let row = Int(y / rowHeight)
guard s > margin else {
return
}
let column: Int
if row.isEven {
column = Int(loc.x / GridViewController.len)
// Abort, if user tapped outside of the grid
if column >= columns {
clearTileSelection()
return
}
} else {
column = Int((loc.x - GridViewController.len / 2) / GridViewController.len)
}
handleTileTapped(tile: row * columns + Int(column))
}
private func handleTileTapped(tile: Int) {
if let selected = selectedTile {
switchTiles(oldTile: selected, newTile: tile)
} else {
showSelection(tile: tile)
}
}
private func showSelection(tile: Int) {
clearTileSelection()
if let view = installedTiles[tile] {
view.borderWidth = 3
view.borderColor = AppDelegate.tintColor
selectedTile = tile
} else {
selectedTile = nil
}
}
private func tileIsVisible(tile: Int, in rect: CGRect) -> Bool {
return rect.intersects(frame(for: tile))
}
private func makeTile(_ tile: Int) {
let view = RoundedImageView(frame: frame(for: tile))
myView.addSubview(view)
defer {
installedTiles[tile] = view
}
// Only set image if images are shown
guard !isShowingColors else {
view.backgroundColor = tileColor(tile: tile)
return
}
if let image = app.database.storage.thumbnail(for: tiles[tile]) {
view.image = image
return
}
downloadImage(tile: tile)
}
private func downloadImage(tile: Int) {
let id = tiles[tile]
downloadImage(cap: id, tile: tile)
}
private func downloadImage(cap id: Int, tile: Int) {
app.database.downloadImage(for: id) { img in
guard img != nil else {
return
}
guard let view = self.installedTiles[tile] else {
self.log("No installed tile for downloaded image \(id)")
return
}
guard let image = app.database.storage.thumbnail(for: id) else {
self.log("Failed to load image for cap \(id) after successful download")
return
}
DispatchQueue.main.async {
guard self.isShowingColors else {
view.image = image
return
}
guard let color = image.averageColor else {
self.log("Failed to get average color from image for cap \(id)")
return
}
view.backgroundColor = color
self.colors[id] = color
}
}
}
private func frame(for tile: Int) -> CGRect {
let row = tile / columns
let column = tile - row * columns
let x = CGFloat(column) * GridViewController.len + (row.isEven ? 0 : GridViewController.len / 2)
let y = CGFloat(row) * rowHeight
return CGRect(x: x, y: y, width: GridViewController.len, height: GridViewController.len)
}
private func switchTiles(oldTile: Int, newTile: Int) {
guard oldTile != newTile else {
clearTileSelection()
return
}
guard switchTiles(oldTile, newTile) else {
clearTileSelection()
return
}
// Switch cap colors
let temp = installedTiles[oldTile]?.backgroundColor
installedTiles[oldTile]?.backgroundColor = installedTiles[newTile]?.backgroundColor
installedTiles[newTile]?.backgroundColor = temp
if !isShowingColors {
let temp = installedTiles[oldTile]?.image
installedTiles[oldTile]?.image = installedTiles[newTile]?.image
installedTiles[newTile]?.image = temp
}
clearTileSelection()
}
private func clearTileSelection() {
guard let tile = selectedTile else {
return
}
installedTiles[tile]?.borderWidth = 0
selectedTile = nil
}
private func showTiles(in rect: CGRect) {
for tile in 0..<tiles.count {
refresh(tile: tile, inVisibleRect: rect)
}
}
private func refresh(tile: Int, inVisibleRect rect: CGRect) {
if tileIsVisible(tile: tile, in: rect) {
show(tile: tile)
} else if let installed = installedTiles[tile] {
installed.removeFromSuperview()
installedTiles[tile] = nil
}
}
private func show(tile: Int) {
guard installedTiles[tile] == nil else {
return
}
makeTile(tile)
}
private func remove(tile: Int) {
installedTiles[tile]?.removeFromSuperview()
installedTiles[tile] = nil
}
private var visibleRect: CGRect {
let scale = scrollView.zoomScale
let offset = scrollView.contentOffset
let size = scrollView.visibleSize
let scaledOrigin = CGPoint(x: offset.x / scale, y: offset.y / scale)
let scaledSize = CGSize(width: size.width / scale, height: size.height / scale)
return CGRect(origin: scaledOrigin, size: scaledSize)
}
private func updateTiles() {
DispatchQueue.main.async {
self.showTiles(in: self.visibleRect)
}
}
}
extension GridViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return myView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
updateTiles()
}
}
private extension Int {
var isEven: Bool {
return self % 2 == 0
}
}
extension GridViewController: Logger { }

View File

@@ -0,0 +1,15 @@
//
// ImageCell.swift
// CapFinder
//
// Created by User on 07.02.18.
// Copyright © 2018 User. All rights reserved.
//
import UIKit
class ImageCell: UICollectionViewCell {
@IBOutlet weak var capView: UIImageView!
}

View File

@@ -0,0 +1,186 @@
//
// ListViewController.swift
// CapFinder
//
// Created by User on 22.02.18.
// Copyright © 2018 User. All rights reserved.
//
import UIKit
class ImageSelector: UIViewController {
// MARK: - Constants
/// The number of items per row
private let itemsPerRow: CGFloat = 3
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .portrait
}
override var shouldAutorotate: Bool {
return false
}
// MARK: - Variables
private var titleLabel: UILabel!
private var subtitleLabel: UILabel!
private var images = [UIImage?]()
var cap: Cap!
weak var imageProvider: ImageProvider?
@IBOutlet weak var collection: UICollectionView!
private var titleText: String {
"Cap \(cap.id) (\(cap.count) images)"
}
private var subtitleText: String {
cap.name
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
collection.dataSource = self
collection.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
downloadImages()
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
guard parent != nil && self.navigationItem.titleView == nil else {
return
}
initNavigationItemTitleView()
}
private func initNavigationItemTitleView() {
self.titleLabel = UILabel()
titleLabel.text = titleText
titleLabel.font = .preferredFont(forTextStyle: .headline)
titleLabel.textColor = .label
self.subtitleLabel = UILabel()
subtitleLabel.text = subtitleText
subtitleLabel.font = .preferredFont(forTextStyle: .footnote)
subtitleLabel.textColor = .secondaryLabel
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.distribution = .equalCentering
stackView.alignment = .center
stackView.axis = .vertical
self.navigationItem.titleView = stackView
}
// MARK: Image download
private func downloadImages() {
images = [UIImage?](repeating: nil, count: cap.count)
log("\(cap.count) images for cap \(cap.id)")
for version in 0..<cap.count {
if let image = imageProvider?.image(for: cap.id, version: version) {
log("Image \(version) already downloaded")
set(image, for: version)
} else {
log("Downloading image \(version)")
app.database.downloadImage(for: cap.id, version: version) { image in
self.set(image, for: version)
if image != nil {
self.log("Downloaded version \(version)")
} else {
self.log("Failed to download image \(version)")
}
}
}
}
}
private func set(_ image: UIImage?, for version: Int) {
self.images[version] = image
DispatchQueue.main.async {
self.collection.reloadItems(at: [IndexPath(row: version, section: 0)])
}
}
// MARK: Select
private func selectedImage(nr: Int) {
app.database.setMainImage(of: cap.id, to: nr)
}
}
// MARK: - UICollectionViewDataSource
extension ImageSelector: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: "Image",
for: indexPath) as! ImageCell
cell.capView.image = images[indexPath.row] ?? UIImage(named: "launch")
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedImage(nr: indexPath.row)
navigationController?.popViewController(animated: true)
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension ImageSelector : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let widthPerItem = collectionView.frame.width / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
extension ImageSelector: Logger { }

View File

@@ -0,0 +1,33 @@
//
// NavigationController.swift
// CapCollector
//
// Created by Christoph on 08.01.19.
// Copyright © 2019 CH. All rights reserved.
//
import UIKit
class NavigationController: UINavigationController {
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return allowLandscape ? .allButUpsideDown : .portrait
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .portrait
}
override var shouldAutorotate: Bool {
return allowLandscape
}
var allowLandscape: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}

View File

@@ -0,0 +1,161 @@
//
// SearchAndDisplayAccessory.swift
// CapCollector
//
// Created by Christoph on 09.10.19.
// Copyright © 2019 CH. All rights reserved.
//
import UIKit
class PassthroughView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
return view == self ? nil : view
}
}
protocol CapAccessoryDelegate: AnyObject {
func capSearchWasDismissed()
func capSearch(didChange text: String)
func capAccessoryDidDiscardImage()
func capAccessory(shouldSave image: UIImage)
func capAccessoryCameraButtonPressed()
}
class SearchAndDisplayAccessory: PassthroughView {
// MARK: - Outlets
@IBOutlet weak var capImage: RoundedImageView!
@IBOutlet weak var saveButton: UIButton!
@IBOutlet weak var cameraButton: UIButton!
@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var imageHeightContraint: NSLayoutConstraint!
// MARK: - Actions
@IBAction func cameraButtonPressed() {
if isShowingCapImage {
discardImage()
} else {
delegate?.capAccessoryCameraButtonPressed()
}
}
@IBAction func saveButtonPressed() {
if let image = capImage.image {
delegate?.capAccessory(shouldSave: image)
}
}
// MARK: - Variables
var view: UIView?
weak var delegate: CapAccessoryDelegate?
var currentImage: UIImage? {
capImage.image
}
var isShowingCapImage: Bool {
capImage.image != nil
}
// MARK: - Setup
convenience init(width: CGFloat) {
let frame = CGRect(origin: .zero, size: CGSize(width: width, height: 145))
self.init(frame: frame)
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
view = fromNib()
view!.frame = bounds
//view!.autoresizingMask = .flexibleHeight
addSubview(view!)
hideImageView()
searchBar.text = nil
searchBar.setShowsCancelButton(false, animated: false)
searchBar.delegate = self
}
// MARK: Search bar
func dismissAndClearSearchBar() {
searchBar.resignFirstResponder()
searchBar.text = nil
}
// MARK: Cap image
func showImageView(with image: UIImage) {
capImage.image = image
cameraButton.setImage(UIImage(systemName: "xmark"), for: .normal)
imageHeightContraint.constant = 90
capImage.alpha = 1
capImage.isHidden = false
saveButton.isHidden = false
}
func discardImage() {
DispatchQueue.main.async {
self.dismissAndClearSearchBar()
self.hideImageView()
}
delegate?.capAccessoryDidDiscardImage()
}
func hideImageView() {
capImage.image = nil
cameraButton.setImage(UIImage(systemName: "camera"), for: .normal)
//imageHeightContraint.constant = 0
capImage.alpha = 0
capImage.isHidden = true
saveButton.isHidden = true
}
}
// MARK: - UISearchBarDelegate
extension SearchAndDisplayAccessory: UISearchBarDelegate {
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
searchBar.text = nil
delegate?.capSearchWasDismissed()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
delegate?.capSearch(didChange: searchText)
}
}

View File

@@ -0,0 +1,112 @@
<?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="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="SearchAndDisplayAccessory" customModule="CapCollector" customModuleProvider="target">
<connections>
<outlet property="cameraButton" destination="bIA-eq-Tn5" id="r0F-0j-Ve9"/>
<outlet property="capImage" destination="vQm-nH-J8o" id="bQK-Vu-Z1U"/>
<outlet property="imageHeightContraint" destination="Phy-Uy-08W" id="gc0-1X-MIz"/>
<outlet property="saveButton" destination="dt5-LD-28a" id="IYT-eN-3lb"/>
<outlet property="searchBar" destination="bCh-7y-t0w" id="8Tt-4h-Fkg"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="PassthroughView" customModule="CapCollector" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="145"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="B0c-fn-aK3">
<rect key="frame" x="0.0" y="90" width="414" height="55"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="Arf-oz-vtV">
<rect key="frame" x="0.0" y="0.0" width="414" height="55"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<blurEffect style="regular"/>
</visualEffectView>
<searchBar contentMode="redraw" searchBarStyle="minimal" placeholder="Search caps" translucent="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bCh-7y-t0w">
<rect key="frame" x="0.0" y="90" width="359" height="55"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" returnKeyType="search" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
<scopeButtonTitles>
<string>Title</string>
<string>Title</string>
</scopeButtonTitles>
</searchBar>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="vQm-nH-J8o" customClass="RoundedImageView" customModule="CapCollector" customModuleProvider="target">
<rect key="frame" x="5" y="0.0" width="90" height="90"/>
<constraints>
<constraint firstAttribute="width" secondItem="vQm-nH-J8o" secondAttribute="height" multiplier="1:1" id="WHb-tV-k4S"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="color" keyPath="borderColor">
<color key="value" systemColor="secondaryLabelColor"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
<real key="value" value="1"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dt5-LD-28a" userLabel="Save Button">
<rect key="frame" x="5" y="0.0" width="90" height="90"/>
<constraints>
<constraint firstAttribute="height" constant="90" id="Phy-Uy-08W"/>
</constraints>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<connections>
<action selector="saveButtonPressed" destination="-1" eventType="touchUpInside" id="O49-6L-mNY"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bIA-eq-Tn5" userLabel="Camera/Clear Button">
<rect key="frame" x="359" y="90" width="55" height="55"/>
<constraints>
<constraint firstAttribute="width" secondItem="bIA-eq-Tn5" secondAttribute="height" multiplier="1:1" id="O09-ww-bHE"/>
</constraints>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle0"/>
<state key="normal" image="camera" catalog="system">
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
</state>
<connections>
<action selector="cameraButtonPressed" destination="-1" eventType="touchUpInside" id="ooo-b8-Atj"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<constraints>
<constraint firstItem="dt5-LD-28a" firstAttribute="top" secondItem="vQm-nH-J8o" secondAttribute="top" id="0fL-ow-Unw"/>
<constraint firstItem="dt5-LD-28a" firstAttribute="leading" secondItem="vQm-nH-J8o" secondAttribute="leading" id="BDw-lR-hYd"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="B0c-fn-aK3" secondAttribute="bottom" id="Cbb-yb-0av"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="bIA-eq-Tn5" secondAttribute="bottom" id="Geo-F0-pdI"/>
<constraint firstItem="bIA-eq-Tn5" firstAttribute="top" secondItem="bCh-7y-t0w" secondAttribute="top" id="IiT-eG-qfb"/>
<constraint firstItem="vQm-nH-J8o" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="5" id="Kf4-2d-yxI"/>
<constraint firstItem="bCh-7y-t0w" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="M9M-He-vxM"/>
<constraint firstItem="vQm-nH-J8o" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="PPc-Zp-Vty"/>
<constraint firstItem="B0c-fn-aK3" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="Pp8-wt-rM7"/>
<constraint firstItem="dt5-LD-28a" firstAttribute="bottom" secondItem="vQm-nH-J8o" secondAttribute="bottom" id="VD3-fH-dsS"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="B0c-fn-aK3" secondAttribute="trailing" id="bri-jC-TDo"/>
<constraint firstItem="dt5-LD-28a" firstAttribute="trailing" secondItem="vQm-nH-J8o" secondAttribute="trailing" id="eg2-RT-iVc"/>
<constraint firstItem="bIA-eq-Tn5" firstAttribute="leading" secondItem="bCh-7y-t0w" secondAttribute="trailing" id="i7w-LO-9lv"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="bIA-eq-Tn5" secondAttribute="trailing" id="kQV-mX-EoD"/>
<constraint firstItem="B0c-fn-aK3" firstAttribute="top" secondItem="bCh-7y-t0w" secondAttribute="top" id="l7d-ZC-pNp"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="bCh-7y-t0w" secondAttribute="bottom" id="r8c-kO-KT5"/>
<constraint firstItem="bCh-7y-t0w" firstAttribute="top" secondItem="vQm-nH-J8o" secondAttribute="bottom" id="tBl-Ig-g9a"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="96" y="104.7976011994003"/>
</view>
</objects>
<resources>
<image name="camera" catalog="system" width="128" height="94"/>
<systemColor name="secondaryLabelColor">
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

View File

@@ -0,0 +1,113 @@
//
// SortController.swift
// CapCollector
//
// Created by Christoph on 12.11.18.
// Copyright © 2018 CH. All rights reserved.
//
import UIKit
enum SortCriteria: Int {
case id = 0
case name = 1
case count = 2
case match = 3
var text: String {
switch self {
case .id:
return "Id"
case .name:
return "Name"
case .count:
return "Count"
case .match:
return "Match"
}
}
}
protocol SortControllerDelegate: AnyObject {
func sortController(didSelect sortType: SortCriteria, ascending: Bool)
}
class SortController: UITableViewController {
@IBOutlet weak var thirdRowLabel: UILabel!
var selected: SortCriteria = .id
var ascending: Bool = false
weak var delegate: SortControllerDelegate?
var options = [SortCriteria]()
override func viewDidLoad() {
super.viewDidLoad()
preferredContentSize = CGSize(width: 200, height: 139 + options.count * 40)
}
private func giveFeedback(_ style: UIImpactFeedbackGenerator.FeedbackStyle) {
UIImpactFeedbackGenerator(style: style).impactOccurred()
}
private func sortCriteria(for index: Int) -> SortCriteria {
index < options.count ? options[index] : .match
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
section == 0 ? "Sort order" : "Sort by"
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
section == 0 ? 1 : options.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SortCell")!
guard indexPath.section != 0 else {
cell.accessoryType = ascending ? .checkmark : .none
cell.textLabel?.text = "Ascending"
return cell
}
let select = sortCriteria(for: indexPath.row)
cell.textLabel?.text = select.text
guard select == selected else {
cell.accessoryType = .none
return cell
}
cell.accessoryType = .checkmark
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
40
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard indexPath.section == 1 else {
ascending = !ascending
tableView.reloadRows(at: [indexPath], with: .automatic)
delegate?.sortController(didSelect: selected, ascending: ascending)
giveFeedback(.light)
return
}
giveFeedback(.medium)
selected = sortCriteria(for: indexPath.row)
tableView.reloadRows(at: [indexPath], with: .automatic)
delegate?.sortController(didSelect: selected, ascending: ascending)
self.dismiss(animated: true)
}
}