diff --git a/Apps/Locations/Sources/View Components/ErrorMessageView.swift b/Apps/Locations/Sources/View Components/ErrorMessageView.swift new file mode 100644 index 0000000..d161f77 --- /dev/null +++ b/Apps/Locations/Sources/View Components/ErrorMessageView.swift @@ -0,0 +1,124 @@ +// +// ErrorMessageView.swift +// Locations +// +// Created by Javier Cicchelli on 12/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import UIKit + +class ErrorMessageView: UIView { + + // MARK: Typealiases + + typealias OnRetryClosure = () -> Void + + // MARK: Properties + + var onRetry: OnRetryClosure? + + // MARK: Outlets + + private lazy var stack: UIStackView = { + let stack = UIStackView() + + stack.alignment = .center + stack.axis = .vertical + stack.distribution = .fill + stack.spacing = 32 + stack.translatesAutoresizingMaskIntoConstraints = false + + return stack + }() + + private lazy var title = { + let title = UILabel() + + title.font = .preferredFont(forTextStyle: .largeTitle) + title.numberOfLines = 0 + title.lineBreakMode = .byWordWrapping + title.text = "Some error title goes in here..." + title.textAlignment = .center + title.translatesAutoresizingMaskIntoConstraints = false + + return title + }() + + private lazy var message = { + let message = UILabel() + + message.font = .preferredFont(forTextStyle: .body) + message.lineBreakMode = .byWordWrapping + message.numberOfLines = 0 + message.text = "Some long, descriptive, explanatory error message goes in here..." + message.textAlignment = .center + message.textColor = .secondaryLabel + message.translatesAutoresizingMaskIntoConstraints = false + + return message + }() + + private lazy var retry = { + let retry = UIButton() + + retry.backgroundColor = .red + retry.translatesAutoresizingMaskIntoConstraints = false + retry.layer.borderColor = UIColor.red.cgColor + retry.layer.borderWidth = 1 + retry.layer.cornerRadius = 5 + retry.titleLabel?.font = .preferredFont(forTextStyle: .headline) + + retry.addTarget(self, action: #selector(retryPressed), for: .touchUpInside) + retry.setTitle("Try again", for: .normal) + + return retry + }() + + // MARK: Initialisers + + init() { + super.init(frame: .zero) + + setupView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +// MARK: - Helpers + +private extension ErrorMessageView { + + // MARK: Functions + + func setupView() { + stack.addArrangedSubview(title) + stack.addArrangedSubview(message) + stack.addArrangedSubview(retry) + stack.setCustomSpacing(160, after: message) + + backgroundColor = .clear + translatesAutoresizingMaskIntoConstraints = false + + addSubview(stack) + + NSLayoutConstraint.activate([ + retry.heightAnchor.constraint(equalToConstant: 44), + retry.leadingAnchor.constraint(equalTo: stack.leadingAnchor), + retry.trailingAnchor.constraint(equalTo: stack.trailingAnchor), + bottomAnchor.constraint(equalTo: stack.bottomAnchor), + leadingAnchor.constraint(equalTo: stack.leadingAnchor), + topAnchor.constraint(equalTo: stack.topAnchor), + trailingAnchor.constraint(equalTo: stack.trailingAnchor) + ]) + } + + @objc func retryPressed() { + onRetry?() + } + +} diff --git a/Apps/Locations/Sources/View Components/LoadingSpinnerView.swift b/Apps/Locations/Sources/View Components/LoadingSpinnerView.swift index c6865e2..461613b 100644 --- a/Apps/Locations/Sources/View Components/LoadingSpinnerView.swift +++ b/Apps/Locations/Sources/View Components/LoadingSpinnerView.swift @@ -12,8 +12,8 @@ class LoadingSpinnerView: UIView { // MARK: Outlets - lazy var stack: UIStackView = { - let stack = UIStackView(frame: .zero) + private lazy var stack = { + let stack = UIStackView() stack.alignment = .center stack.axis = .vertical @@ -24,7 +24,7 @@ class LoadingSpinnerView: UIView { return stack }() - lazy var spinner: UIActivityIndicatorView = { + private lazy var spinner = { let spinner = UIActivityIndicatorView(style: .large) spinner.translatesAutoresizingMaskIntoConstraints = false @@ -34,15 +34,16 @@ class LoadingSpinnerView: UIView { return spinner }() - lazy var label: UILabel = { + private lazy var label = { let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false + label.font = .preferredFont(forTextStyle: .headline) - label.text = "Loading..." - label.textAlignment = .center label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping + label.text = "Loading..." + label.textAlignment = .center + label.translatesAutoresizingMaskIntoConstraints = false return label }() @@ -68,12 +69,12 @@ private extension LoadingSpinnerView { // MARK: Functions func setupView() { - backgroundColor = .clear - translatesAutoresizingMaskIntoConstraints = false - stack.addArrangedSubview(spinner) stack.addArrangedSubview(label) + backgroundColor = .clear + translatesAutoresizingMaskIntoConstraints = false + addSubview(stack) NSLayoutConstraint.activate([ diff --git a/DeepLinking.xcodeproj/project.pbxproj b/DeepLinking.xcodeproj/project.pbxproj index 2be6b99..f83395d 100644 --- a/DeepLinking.xcodeproj/project.pbxproj +++ b/DeepLinking.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 02031EC629E5FEE4003C108C /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02031EC529E5FEE4003C108C /* BaseViewController.swift */; }; 02031EC929E60B29003C108C /* DependencyService+Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02031EC829E60B29003C108C /* DependencyService+Keys.swift */; }; 02031EE829E68D9B003C108C /* LoadingSpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02031EE729E68D9B003C108C /* LoadingSpinnerView.swift */; }; + 02031EEA29E6B495003C108C /* ErrorMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02031EE929E6B495003C108C /* ErrorMessageView.swift */; }; 46C3B7C629E5BF1500F8F57C /* LocationsListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C3B7C529E5BF1500F8F57C /* LocationsListCoordinator.swift */; }; 46C3B7CB29E5CD3200F8F57C /* LocationsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C3B7CA29E5CD3200F8F57C /* LocationsListViewModel.swift */; }; 46C3B7CF29E5D00E00F8F57C /* LocationsAddViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C3B7CE29E5D00E00F8F57C /* LocationsAddViewModel.swift */; }; @@ -126,6 +127,7 @@ 02031EC529E5FEE4003C108C /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; 02031EC829E60B29003C108C /* DependencyService+Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DependencyService+Keys.swift"; sourceTree = ""; }; 02031EE729E68D9B003C108C /* LoadingSpinnerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingSpinnerView.swift; sourceTree = ""; }; + 02031EE929E6B495003C108C /* ErrorMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessageView.swift; sourceTree = ""; }; 46C3B7C529E5BF1500F8F57C /* LocationsListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsListCoordinator.swift; sourceTree = ""; }; 46C3B7CA29E5CD3200F8F57C /* LocationsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsListViewModel.swift; sourceTree = ""; }; 46C3B7CE29E5D00E00F8F57C /* LocationsAddViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsAddViewModel.swift; sourceTree = ""; }; @@ -180,6 +182,7 @@ isa = PBXGroup; children = ( 02031EE729E68D9B003C108C /* LoadingSpinnerView.swift */, + 02031EE929E6B495003C108C /* ErrorMessageView.swift */, ); path = "View Components"; sourceTree = ""; @@ -526,6 +529,7 @@ 46EB331B29E1CE04001D5EAF /* AppDelegate.swift in Sources */, 02031EBF29E5F949003C108C /* LocationsAddViewModeling.swift in Sources */, 46C3B7DE29E5ED2E00F8F57C /* LocationsAddCoordinator.swift in Sources */, + 02031EEA29E6B495003C108C /* ErrorMessageView.swift in Sources */, 46C3B7DC29E5ED2300F8F57C /* LocationsAddCoordination.swift in Sources */, 46C3B7D829E5E55000F8F57C /* LocationsListCoordination.swift in Sources */, 46C3B7D629E5E50500F8F57C /* LocationsListViewModeling.swift in Sources */,