Contents

moslienko/appviewutilits

A collection of useful extensions and classes for working on a iOS app projects that have been used in them many times.

Table of Contents

AppView AppAlert Placeholder Cells Table Cell Collection Cell Enhanced UIKit elements AppContentSizedTableView AppButton AppLabel AppTextField AppTextView AppSpacerView AppLoaderView AppTouchLabel AppScrollView AppStackView AppBarButtonItem

Debug Log Measure TimeLogger Threads DebouncedFunction RecursionFunction ThreadSwitching AppVersion Decorate Environment Reachability Callback NotificationObserver AppThemeManager AppLayout * EventObject

Installation

Swift Package Manager

To integrate using Apple's Swift Package Manager, add the following as a dependency to your Package.swift:

dependencies: [
    .package(url: "https://github.com/moslienko/AppViewUtilits.git", from: "1.3.1")
]

Alternatively navigate to your Xcode project, select Swift Packages and click the + icon to search for AppViewUtilits.

Manually

If you prefer not to use any of the aforementioned dependency managers, you can integrate AppViewUtilits into your project manually. Simply drag the Sources Folder into your Xcode project.

Extensions

There are extensions to the types:

| | | | | |------------------------------------------------------------|----------------------------------------------------------|-----------------------------------------------------------|----------------------------------------------------------| | Array | AVPlayer | UIButton | CALayer | | CAAnimation | CGFloat | Collection | Color | | Date | Dictionary | Double | Encodable | | FileManager | UIImage | UIImageView | Int | | Label | NSAttributedString | NSCountedSet | NSPredicate | | Notification | Optional | Sequence | SKProduct | | String | UICollectionView | UIColor | UIApplication | | UIBezierPath | UILabel | UITableView | UITextField | | UITabBar | UIView | UIViewController | UIStackView | | UIWindow | UIScrollView | View | |

Components

AppView

AppView - this is a set of classes to facilitate work with a UIView & UIViewController. The following classes are available for use:

  • AppView (UIView)
  • AppViewModel - model class for AppView
  • AppViewController (UIViewController).

AppView also supports handling of taps and swipes:

func addTap(_ action: @escaping DataCallback<CGPoint>)
func addDoubleTap(_ action: @escaping DataCallback<CGPoint>)
func addLongPress(minimumPressDuration: TimeInterval = 0, action: @escaping DataCallback<UILongPressGestureRecognizer>)
func addPan(_ panAction: @escaping DataCallback<UIPanGestureRecognizer>)
func addSwipeGestureRecognizer(directions: UISwipeGestureRecognizer.Direction, action: @escaping DataCallback<UISwipeGestureRecognizer.Direction>)

AppAlert

AppAlertController - custom UIAlertController subclass for presenting alerts.

struct AppAlertViewModel {
  let title: String?
  let message: String?
  let actions: [AlertAction]?
  let preferredStyle: UIAlertController.Style
  var configureTextField: ((UITextField) -> Void)?
  var tintColor: UIColor
  var preventMultipleAlerts: Bool
}

Presents alert:

static func presentAlert(using model: AppAlertViewModel)

Or initialize the creation AppAlertController: to show the alert on the screen in your custom method:

static func createAppAlertController(with model: AppAlertViewModel) -> AppAlertController

Placeholder

Displaying the customizable placeholder. Initializes a new PlaceholderView:

init(
    type: PlaceholderType,
    options: PlaceholderOptions = PlaceholderOptions(),
    didActionButtonTapped: Callback? = nil
)

Set the style for the placeholder UI elements:

func decorate(
  titleLabelStyle: DecorateWrapper<UILabel> = .defaultPlaceholderHeaderStyle(),
  messageLabelStyle: DecorateWrapper<UILabel> = .defaultPlaceholderMessageStyle(),
  buttonRegularStyle: DecorateWrapper<AppButton> = .defaultPlaceholderButtonStyle(),
  buttonDisabledStyle: DecorateWrapper<AppButton>? = nil,
  highlightedStyle: DecorateWrapper<AppButton>? = nil
)

Display the placeholder on the screen:

func addPlaceholder(_ view: PlaceholderView)

Remove the placeholder from the screen:

func removePlaceholder()

<details> <summary>Example of use:</summary>

enum AppPlaceholder: PlaceholderType {
  case emptyList

  var title: String {
    switch self {
    case .emptyList:
      return "Empty list"
    }
  }

  var message: String? {
    switch self {
    case .emptyList:
      return "You don't have any entries yet"
    }
  }

  var buttonTitle: String? {
    switch self {
    case .emptyList:
      return "Close"
    }
  }

  var icon: UIImage? {
    switch self {
    case .emptyList:
      return UIImage(systemName: "list.clipboard")
    }
  }
}

private(set) lazy var placeholderView: PlaceholderView = {
  let view = PlaceholderView(
    type: AppPlaceholder.emptyList,
    options: PlaceholderOptions(),
    didActionButtonTapped: { [weak self] in
    }
  )
  view.decorate(
    buttonRegularStyle: .wrap(style: { button in
      button.backgroundColor = .systemGreen
      button.tintColor = .white
      button.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium)
      button.layer.cornerRadius = 15
    }),
    highlightedStyle: .wrap(style: { button in
      button.backgroundColor = .systemGreen.withAlphaComponent(0.7)
    })
  )
  return view
}()

...

if !self.models.isEmpty {
  self.tableView.removePlaceholder()
  self.tableView.reloadData()
} else {
  self.tableView.reloadData()
  self.tableView.addPlaceholder(self.placeholderView)
}

</details>

Cells

Table Cell

AppViewTableCell - class for table cell.

```swift class ExampleCell: AppViewTableCell<ExampleCellModel> {

override func updateView() { guard let cellModel = cellModel else { return }

self.textLabel?.text = cellModel.title self.layoutIfNeeded() }

override func prepareForReuse() { super.prepareForReuse() } }

class ExampleCellModel: AppViewCellIdentifiable {

let title: String

init(title: String) { self.title = title } }


Register cell:

self.tableView.registerCellClass(ExampleCell.self)


Display cell:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let model = models[safe: indexPath.section]?[safe: indexPath.row] else { return UITableViewCell() } let cell = tableView.dequeueReusableCell(with: ExampleCell.self, for: indexPath) cell.cellModel = model as? ExampleCellModel

return cell }


#### Collection Cell

`AppViewCollectionCell` - class for collection cell.

### Enhanced UIKit elements
#### AppContentSizedTableView

A `UITableView` subclass that automatically adjusts its intrinsic content size based on its content size.

AppContentSizedTableView: UITableView {}


#### AppButton
 `UIButton` based class with useful extras.
 Can be used in conjunction with `DecorateWrapper` to automatic set different styles for all button states.

var regularStyle: DecorateWrapper<AppButton>? var disabledStyle: DecorateWrapper<AppButton>? var highlightedStyle: DecorateWrapper<AppButton>?


Handling actions.

let button = AppButton(title: "Action") button.addAction {} button.addTapAnimation(didHighlight: {}, didUnhighlight: {})


#### AppLabel

Subclass `UILabel`. 

init(text: String)


#### AppTextField
Subclass `UITextField`.

var textInsets: UIEdgeInsets var maxCharacters: Int?

var didTextChanged: DataCallback<String>? var didBeginEditing: DataCallback<UITextField>? var didEndEditing: DataCallback<UITextField>? var shouldBeginEditing: ReturnedDataCallback<UITextField, Bool>? var shouldEndEditing: ReturnedDataCallback<UITextField, Bool>? var shouldClear: ReturnedDataCallback<UITextField, Bool>? var shouldReturn: ReturnedDataCallback<UITextField, Bool>? var shouldChangeCharactersIn: ReturnedDataCallback<(UITextField, NSRange, String), Bool>?


#### AppTextView
Subclass `UITextView`. Callbacks similar to `AppTextField` are used, setting the `maxCharacters` value, placeholder setting.

var placeholder: String? var placeholderColor: UIColor var placeholderFont: UIFont var placeholderFrame: CGRect?


#### AppSpacerView
Helpful `UIView` subclass for displaying a spacer component, e.g. as an additional element in a `UIStackView`.

enum Direction { case vertical(CGFloat) case horizontal(CGFloat) }

init( direction: Direction? = nil) static func createView( direction: AppSpacerView.Direction, color: UIColor) -> AppSpacerView static func createView(_ direction: AppSpacerView.Direction, color: UIColor, width: Double)


#### AppLoaderView
 `UIVIew` subclass for displaying the indicator.
 

func configureActivityView( style: UIActivityIndicatorView.Style, color: UIColor, backgroundColor: UIColor ) func beginLoading() func finishLoading()


#### AppTouchLabel
`UILabel` subclass with the ability to tap on it.

init(action: @escaping Callback)


#### AppScrollView
Subclass `UIScrollView`.

func scrollToTop(animated: Bool) func scrollToBottom(animated: Bool) func scrollToLeft(animated: Bool) func scrollToRight(animated: Bool) func setScrollIndicatorsVisibility(vertical: Bool, horizontal: Bool)


#### AppStackView
Subclass `UIStackView`.

init( _ subviews: [UIView], axis: NSLayoutConstraint.Axis, spacing: CGFloat = 0, distribution: Distribution = .fill )


#### AppBarButtonItem
Subclass `UIBarButtonItem`.

init( title: String, style: UIBarButtonItem.Style, action: Callback? )

init( icon: UIImage?, style: UIBarButtonItem.Style, action: Callback? )

Helpers

Debug

Log

Logger to the console.

Log.debug("Started")
Log.info("Success finished")
Log.error("Failed \(error)")
Measure

A utility class for measuring execution time and memory usage of code blocks.

Measures the execution time of a synchronous block of code.

func measureExecutionTime(
    label: String = "Execution Time",
    _ block: () -> Void
)

Measures the execution time of an asynchronous block of code.

func measureAsyncExecutionTime(
    label: String = "Async Operation",
    _ block: (@escaping () -> Void) -> Void
)

Measures the average execution time of a block of code over multiple runs.

func measureAverageExecutionTime(
    repeatCount: Int,
    label: String = "Average Execution Time",
    _ block: () -> Void
)

Measures the memory usage of a block of code.

func measureMemoryUsage(
    label: String = "Memory Usage",
    _ block: () -> Void
)
TimeLogger

A utility class for logging the time taken by various sections of code.

Starts timing a section of code.

func startSection(_ section: String)

Ends timing a section of code and logs the elapsed time.

func endSection(_ section: String)

Threads

DebouncedFunction

Provide debouncing functionality.

let method = DebouncedFunction(delay: 1.0) {
    Log.debug("Called")
}
method.call()
RecursionFunction

Performs a recursive operation with a condition check and retry mechanism. Example:

RecursionFunction(maxRetryCount: 5)
  .performRecursion(
    isCondition: {
      Int.random(in: 0...5) == 3
    },
    didHandleMethod: {
	   Log.debug("Recursion success handler")
    },
    delay: 3.0
  )
ThreadSwitching

Easy work with threads.

onMainThread {}
onBgThread {}

AppVersion

Application version information.

AppVersion.getInfo()

Result:

struct Info {
  var appName: String
  var version: String
  var build: String
  var target: String
}

Decorate

Wrapper for setting UI element styles.

extension DecorateWrapper where Element: UILabel {
    static func headerStyle() -> DecorateWrapper {
        return .wrap { label in
            label.textColor = .black
            label.font = UIFont.boldSystemFont(ofSize: 26.0)
            label.textAlignment = .left
            label.lineBreakMode = .byWordWrapping
            label.numberOfLines = 2
        }
    }
}

Example call:

titleLabel.apply(.headerStyle())

Environment

Environment.current

Result:

enum Environment {
  case debug
  case release
  case testFlight
}

Reachability

Internet connection checker.

Reachability.isConnectedToNetwork()

Callback

typealias Callback = () -> Void
typealias DataCallback<T> = (T) -> Void
typealias ValueCallback<T> = () -> T
typealias ReturnedDataCallback<A, B> = (A) -> B

NotificationObserver

Helper class for working with NSNotification.

func addObserver(
  forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue? = .main,
  using block: @escaping (Notification) -> Void)
func removeObserver(_ observer: AnyObject)
func removeAllObservers()

AppThemeManager

A manager for dynamically changing the apps appearance by overriding the user interface style on all screens. Example:

var isDarkTheme = false {
  didSet {
    AppThemeManager.default.setAppTheme()
  }
}

...

AppThemeManager.default.setOverrideUserInterfaceStyleCallback = {
  self.isDarkTheme ? .dark : .light
}

AppLayout

[[stability-experimental]](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#experimental)

Building an interface through code.

Example:

let listItemView = UIView()

listItemView.layout
  .addToSuperview(self.view)
  .height(64.0)
  .top(to: self.view.safeAreaLayoutGuide.topAnchor)
  .leading(to: self.view.leadingAnchor)
  .trailing(to: self.view.trailingAnchor)

EventObject

Class for sending and receiving events via Combine.

```swift public protocol EventReceiver { associatedtype Event

func subscribe(receiveValue: @escaping (Event) -> Void) -> AnyCancellable func subscribe<S: Scheduler>( on scheduler: S, receiveValue: @escaping (Event) -> Void ) -> AnyCancellable }

public protocol EventNotifier { associatedtype Event

func send(_ event: Event) var receiver: any EventReceiver { get } }

public class EventObject<Event>: EventNotifier, EventReceiver {}

```

License

AppViewUtilits
Copyright (c) 2020 Pavel Moslienko moslienko@icloud.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Package Metadata

Repository: moslienko/appviewutilits

Default branch: master

README: README.md