Rename repo and app
This commit is contained in:
39
Caps/Presentation/CapCell.swift
Normal file
39
Caps/Presentation/CapCell.swift
Normal 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
|
||||
}
|
||||
}
|
360
Caps/Presentation/GridViewController.swift
Normal file
360
Caps/Presentation/GridViewController.swift
Normal 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 { }
|
15
Caps/Presentation/ImageCell.swift
Normal file
15
Caps/Presentation/ImageCell.swift
Normal 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!
|
||||
|
||||
}
|
186
Caps/Presentation/ImageSelector.swift
Normal file
186
Caps/Presentation/ImageSelector.swift
Normal 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 { }
|
||||
|
||||
|
33
Caps/Presentation/NavigationController.swift
Normal file
33
Caps/Presentation/NavigationController.swift
Normal 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.
|
||||
}
|
||||
|
||||
}
|
161
Caps/Presentation/SearchAndDisplayAccessory.swift
Normal file
161
Caps/Presentation/SearchAndDisplayAccessory.swift
Normal 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)
|
||||
}
|
||||
}
|
112
Caps/Presentation/SearchAndDisplayAccessory.xib
Normal file
112
Caps/Presentation/SearchAndDisplayAccessory.xib
Normal 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>
|
113
Caps/Presentation/SortController.swift
Normal file
113
Caps/Presentation/SortController.swift
Normal 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)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user