From 2718792c67e64fe36c6bafc090fa0ffb9c5600e1 Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Thu, 30 Jul 2026 01:38:05 +0200 Subject: [PATCH] ux: use UITableView for dropdowns in reader to be consistent with settings --- ImageViewer/ViewController.swift | 296 ++++++++++++------------------- 1 file changed, 109 insertions(+), 187 deletions(-) diff --git a/ImageViewer/ViewController.swift b/ImageViewer/ViewController.swift index fc86200..65a8fa4 100644 --- a/ImageViewer/ViewController.swift +++ b/ImageViewer/ViewController.swift @@ -91,10 +91,8 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { let bottomBarView = UIView() let info = UILabel() - let pageTurnDropdownView = UIView() let pageTurnDropdownButton = UIButton() - let backgroundColorDropdownView = UIView() let backgroundColorDropdownButton = UIButton() let homeButton = UIButton() @@ -347,12 +345,12 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { func convertStringToColor(_ str: String) -> UIColor { let r: UIColor! - switch str { - case "White": r = .white - case "Gray": r = .gray - case "Black": r = .black - case "Red": r = .red - case "Blue": r = .blue + switch str.lowercased() { + case "white": r = .white + case "gray": r = .gray + case "black": r = .black + case "red": r = .red + case "blue": r = .blue default: r = .black } return r @@ -1094,44 +1092,15 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { } func setupBackgroundColorDropdown() { - backgroundColorDropdownButton.setTitle("Background color ▼", for: .normal) + backgroundColorDropdownButton.setTitle("Background Color", for: .normal) backgroundColorDropdownButton.setTitleColor(.white, for: .normal) backgroundColorDropdownButton.translatesAutoresizingMaskIntoConstraints = false topBarView.addSubview(backgroundColorDropdownButton) backgroundColorDropdownButton.addTarget( - self, action: #selector(toggleBackgroundColorDropdown), for: .touchDown + self, action: #selector(backgroundColorTapped), for: .touchDown ) - backgroundColorDropdownView.backgroundColor = UIColor.darkGray - backgroundColorDropdownView.translatesAutoresizingMaskIntoConstraints = false - backgroundColorDropdownView.isHidden = true - readerView.addSubview(backgroundColorDropdownView) - - let stackView = UIStackView() - stackView.axis = .vertical - stackView.spacing = 8 - stackView.distribution = .fillEqually - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.isLayoutMarginsRelativeArrangement = true - stackView.layoutMargins = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0) - backgroundColorDropdownView.addSubview(stackView) - - let colorOptions = ["White", "Gray", "Black", "Red", "Blue", "Default"] - for title in colorOptions { - let button = UIButton(type: .system) - button.setTitle(title, for: .normal) - button.setTitleColor(.white, for: .normal) - button.contentHorizontalAlignment = .left - button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 0) - button.backgroundColor = .clear - button.translatesAutoresizingMaskIntoConstraints = false - button.addTarget( - self, action: #selector(handleBackgroundColorOption), for: .touchDown - ) - stackView.addArrangedSubview(button) - } - NSLayoutConstraint.activate([ backgroundColorDropdownButton.trailingAnchor.constraint( equalTo: topBarView.trailingAnchor, constant: -32 @@ -1145,67 +1114,19 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { backgroundColorDropdownButton.bottomAnchor.constraint( equalTo: topBarView.bottomAnchor ), - - backgroundColorDropdownView.topAnchor.constraint( - equalTo: backgroundColorDropdownButton.bottomAnchor - ), - backgroundColorDropdownView.leadingAnchor.constraint( - equalTo: backgroundColorDropdownButton.leadingAnchor - ), - backgroundColorDropdownView.trailingAnchor.constraint( - equalTo: backgroundColorDropdownButton.trailingAnchor - ), - - stackView.topAnchor.constraint( - equalTo: backgroundColorDropdownView.topAnchor - ), - stackView.bottomAnchor.constraint(equalTo: backgroundColorDropdownView.bottomAnchor), - stackView.leadingAnchor.constraint(equalTo: backgroundColorDropdownView.leadingAnchor), - stackView.trailingAnchor.constraint( - equalTo: backgroundColorDropdownView.trailingAnchor - ), ]) } func setupPageTurnDropdown() { - pageTurnDropdownButton.setTitle("Page Turn Mode ▼", for: .normal) + pageTurnDropdownButton.setTitle("Page Turn Mode", for: .normal) pageTurnDropdownButton.setTitleColor(.white, for: .normal) pageTurnDropdownButton.translatesAutoresizingMaskIntoConstraints = false topBarView.addSubview(pageTurnDropdownButton) pageTurnDropdownButton.addTarget( - self, action: #selector(togglePageTurnDropdown), for: .touchDown + self, action: #selector(pageTurnTapped), for: .touchDown ) - pageTurnDropdownView.backgroundColor = UIColor.darkGray - pageTurnDropdownView.translatesAutoresizingMaskIntoConstraints = false - pageTurnDropdownView.isHidden = true - readerView.addSubview(pageTurnDropdownView) - - let stackView = UIStackView() - stackView.axis = .vertical - stackView.spacing = 8 - stackView.distribution = .fillEqually - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.isLayoutMarginsRelativeArrangement = true - stackView.layoutMargins = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0) - pageTurnDropdownView.addSubview(stackView) - - let pageTurnOptions = ["Left to right", "Right to left", "Scroll", "Default"] - for title in pageTurnOptions { - let button = UIButton(type: .system) - button.setTitle(title, for: .normal) - button.setTitleColor(.white, for: .normal) - button.contentHorizontalAlignment = .left - button.backgroundColor = .clear - button.translatesAutoresizingMaskIntoConstraints = false - button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 0) - button.addTarget( - self, action: #selector(handlePageTurnOption), for: .touchDown - ) - stackView.addArrangedSubview(button) - } - NSLayoutConstraint.activate([ pageTurnDropdownButton.trailingAnchor.constraint( equalTo: backgroundColorDropdownButton.leadingAnchor, constant: -32 @@ -1219,88 +1140,9 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { pageTurnDropdownButton.bottomAnchor.constraint( equalTo: topBarView.bottomAnchor ), - - pageTurnDropdownView.topAnchor.constraint( - equalTo: pageTurnDropdownButton.bottomAnchor - ), - pageTurnDropdownView.leadingAnchor.constraint( - equalTo: pageTurnDropdownButton.leadingAnchor - ), - pageTurnDropdownView.trailingAnchor.constraint( - equalTo: pageTurnDropdownButton.trailingAnchor - ), - - stackView.topAnchor.constraint( - equalTo: pageTurnDropdownView.topAnchor - ), - stackView.bottomAnchor.constraint(equalTo: pageTurnDropdownView.bottomAnchor), - stackView.leadingAnchor.constraint(equalTo: pageTurnDropdownView.leadingAnchor), - stackView.trailingAnchor.constraint( - equalTo: pageTurnDropdownView.trailingAnchor - ), ]) } - @objc func handlePageTurnOption(_ sender: UIButton) { - if let title = sender.currentTitle { - let prev = mode - switch title.lowercased() { - case "left to right": mode = .leftToRight - case "right to left": mode = .rightToLeft - case "scroll": mode = .scroll - case "default": mode = settings.defaultPageTurnMode - default: break - } - if prev == mode { return } - if mode != .scroll { - let archiveURL = getArchiveURL(progress.v) - imageLoader.loadImage( - archiveURL: archiveURL, - filename: getImagePath(progress), - scaling: .scaleAspectFit, - screenSize: UIScreen.main.bounds.size - ) { [weak self] image in - self?.imageView.image = image - } - } - } - scrollingCollectionView.isHidden = mode != .scroll - leftView.isHidden = mode == .scroll - rightView.isHidden = mode == .scroll - imageView.isHidden = mode == .scroll - // This does not optmize for the case where you switch between scrolling and non-scrolling in the same comic. - // However this is badly supported because of the lack of state conversion. - if mode == .scroll { - scrollingCollectionView.reloadData() - } - togglePageTurnDropdown() - saveLocalState() - } - - @objc func handleBackgroundColorOption(_ sender: UIButton) { - if let title = sender.currentTitle { - switch title.lowercased() { - case "white": readerView.backgroundColor = .white - case "gray": readerView.backgroundColor = .gray - case "black": readerView.backgroundColor = .black - case "red": readerView.backgroundColor = .red - case "blue": readerView.backgroundColor = .blue - case "default": readerView.backgroundColor = convertStringToColor(settings.defaultReadingBackgroundColor) - default: break - } - } - toggleBackgroundColorDropdown() - saveLocalState() - } - - @objc func togglePageTurnDropdown() { - pageTurnDropdownView.isHidden.toggle() - } - - @objc func toggleBackgroundColorDropdown() { - backgroundColorDropdownView.isHidden.toggle() - } - func gestureRecognizer( _: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith _: UIGestureRecognizer @@ -1311,11 +1153,6 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { func toggleBar() { topBarView.isHidden.toggle() bottomBarView.isHidden.toggle() - - if topBarView.isHidden { - pageTurnDropdownView.isHidden = true - backgroundColorDropdownView.isHidden = true - } } func scrollViewDidEndDecelerating(_: UIScrollView) { @@ -1335,8 +1172,6 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { func hideBar() { topBarView.isHidden = true bottomBarView.isHidden = true - pageTurnDropdownView.isHidden = true - backgroundColorDropdownView.isHidden = true } @objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) { @@ -2230,7 +2065,7 @@ class SettingsMenuViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") - preferredContentSize = CGSize(width: 440, height: items.count * 44) + preferredContentSize = CGSize(width: 220, height: items.count * 44) } override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { @@ -2259,11 +2094,9 @@ class SettingsMenuViewController: UITableViewController { } extension ViewController { - private func colorLeafItems(applyTo colorHandler: @escaping (UIColor) -> Void) -> [SettingsMenuItem] { - let palette: [(String, UIColor)] = [ - ("Black", .black), ("White", .white), ("Red", .red), ("Blue", .blue), - ] - return palette.map { name, color in + private func colorItems(applyTo colorHandler: @escaping (UIColor) -> Void) -> [SettingsMenuItem] { + let items: [(String, UIColor)] = [("Black", .black), ("White", .white), ("Red", .red), ("Blue", .blue)] + return items.map { name, color in SettingsMenuItem(title: name, children: nil, action: { [weak self] in colorHandler(color) self?.dismiss(animated: false) @@ -2271,11 +2104,37 @@ extension ViewController { } } - private func pageTurnLeafItems(applyTo pageTurnHandler: @escaping (PageTurnMode) -> Void) -> [SettingsMenuItem] { - let palette: [(String, PageTurnMode)] = [ + private func colorDefaultItems(applyTo colorHandler: @escaping (UIColor) -> Void) -> [SettingsMenuItem] { + let items: [(String, UIColor)] = [ + ("Black", .black), ("White", .white), ("Red", .red), ("Blue", .blue), + ("Default", convertStringToColor(settings.defaultReadingBackgroundColor)), + ] + return items.map { name, color in + SettingsMenuItem(title: name, children: nil, action: { [weak self] in + colorHandler(color) + self?.dismiss(animated: false) + }) + } + } + + private func pageTurnItems(applyTo pageTurnHandler: @escaping (PageTurnMode) -> Void) -> [SettingsMenuItem] { + let items: [(String, PageTurnMode)] = [ ("Left to right", .leftToRight), ("Right to left", .rightToLeft), ("Scroll", .scroll), ] - return palette.map { name, pageTurnMode in + return items.map { name, pageTurnMode in + SettingsMenuItem(title: name, children: nil, action: { [weak self] in + pageTurnHandler(pageTurnMode) + self?.dismiss(animated: false) + }) + } + } + + private func pageTurnDefaultItems(applyTo pageTurnHandler: @escaping (PageTurnMode) -> Void) -> [SettingsMenuItem] { + let items: [(String, PageTurnMode)] = [ + ("Left to right", .leftToRight), ("Right to left", .rightToLeft), ("Scroll", .scroll), + ("Default", settings.defaultPageTurnMode), + ] + return items.map { name, pageTurnMode in SettingsMenuItem(title: name, children: nil, action: { [weak self] in pageTurnHandler(pageTurnMode) self?.dismiss(animated: false) @@ -2284,16 +2143,16 @@ extension ViewController { } private func makeSettingsTree() -> [SettingsMenuItem] { - let backgroundColors = colorLeafItems { [weak self] color in + let backgroundColors = colorItems { [weak self] color in self?.homeView.backgroundColor = color self?.settings.homeBackgroundColor = self?.convertColorToString(color) ?? "White" self?.saveSettings() } - let defaultReadingBackgroundColor = colorLeafItems { [weak self] color in + let defaultReadingBackgroundColor = colorItems { [weak self] color in self?.settings.defaultReadingBackgroundColor = self?.convertColorToString(color) ?? "Black" self?.saveSettings() } - let defaultPageTurnMode = pageTurnLeafItems { [weak self] pageTurnMode in + let defaultPageTurnMode = pageTurnItems { [weak self] pageTurnMode in self?.settings.defaultPageTurnMode = pageTurnMode self?.saveSettings() } @@ -2311,6 +2170,39 @@ extension ViewController { return [colorsMenu, pageTurnMenu] } + + private func makePageTurnTree() -> [SettingsMenuItem] { + return pageTurnDefaultItems { [self] pageTurnMode in + if self.mode == pageTurnMode { return } + + if self.mode != .scroll { + let archiveURL = getArchiveURL(progress.v) + imageLoader.loadImage( + archiveURL: archiveURL, + filename: getImagePath(self.progress), + scaling: .scaleAspectFit, + screenSize: UIScreen.main.bounds.size + ) { [weak self] image in + self?.imageView.image = image + } + } + scrollingCollectionView.isHidden = mode != .scroll + leftView.isHidden = mode == .scroll + rightView.isHidden = mode == .scroll + imageView.isHidden = mode == .scroll + if mode == .scroll { + scrollingCollectionView.reloadData() + } + self.mode = pageTurnMode + saveLocalState() + } + } + + private func makeBackgroundColorTree() -> [SettingsMenuItem] { + return colorDefaultItems { [weak self] color in + self?.readerView.backgroundColor = color + } + } } extension ViewController: UIPopoverPresentationControllerDelegate { @@ -2329,6 +2221,36 @@ extension ViewController: UIPopoverPresentationControllerDelegate { present(nav, animated: false) } + @objc private func pageTurnTapped() { + let root = SettingsMenuViewController(title: "Modes", items: makePageTurnTree()) + let nav = UINavigationController(rootViewController: root) + nav.modalPresentationStyle = .popover + + if let popover = nav.popoverPresentationController { + popover.sourceView = pageTurnDropdownButton + popover.sourceRect = pageTurnDropdownButton.bounds + popover.permittedArrowDirections = .up + popover.delegate = self + } + + present(nav, animated: false) + } + + @objc private func backgroundColorTapped() { + let root = SettingsMenuViewController(title: "Colors", items: makeBackgroundColorTree()) + let nav = UINavigationController(rootViewController: root) + nav.modalPresentationStyle = .popover + + if let popover = nav.popoverPresentationController { + popover.sourceView = backgroundColorDropdownButton + popover.sourceRect = backgroundColorDropdownButton.bounds + popover.permittedArrowDirections = .up + popover.delegate = self + } + + present(nav, animated: false) + } + func adaptivePresentationStyle(for _: UIPresentationController) -> UIModalPresentationStyle { return .none // keeps it a real popover on iPhone instead of a full-screen sheet }