Move local modules into a folder named Modules.

This commit is contained in:
Brent Simmons
2024-07-06 21:07:05 -07:00
parent 14bcef0f9a
commit d50b5818ac
491 changed files with 76 additions and 52 deletions

View File

@@ -0,0 +1,27 @@
//
// Animations.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 1/27/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import Foundation
/// Used to select which animations should be performed
public struct Animations: OptionSet, Sendable {
/// Select and deslections will be animated.
public static let select = Animations(rawValue: 1)
/// Scrolling will be animated
public static let scroll = Animations(rawValue: 2)
/// Pushing and popping navigation view controllers will be animated
public static let navigation = Animations(rawValue: 4)
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
}

View File

@@ -0,0 +1,20 @@
//
// NonIntrinsicButton.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 8/25/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import Foundation
import UIKit
public final class NonIntrinsicButton: UIButton {
// Prevent autolayout from messing around with our frame settings
public override var intrinsicContentSize: CGSize {
return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
}
}

View File

@@ -0,0 +1,18 @@
//
// NonIntrinsicImageView.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 4/22/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
public final class NonIntrinsicImageView: UIImageView {
// Prevent autolayout from messing around with our frame settings
public override var intrinsicContentSize: CGSize {
return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
}
}

View File

@@ -0,0 +1,18 @@
//
// NonIntrinsicLabel.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 4/22/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
public final class NonIntrinsicLabel: UILabel {
// Prevent autolayout from messing around with our frame settings
public override var intrinsicContentSize: CGSize {
return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
}
}

View File

@@ -0,0 +1,20 @@
//
// RoundedProgressView.swift
// NetNewsWire
//
// Created by Maurice Parker on 10/29/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
public final class RoundedProgressView: UIProgressView {
public override func layoutSubviews() {
super.layoutSubviews()
for subview in subviews {
subview.layer.masksToBounds = true
subview.layer.cornerRadius = bounds.height / 2.0
}
}
}

View File

@@ -0,0 +1,24 @@
//
// String+Extensions.swift
// NetNewsWire
//
// Created by Maurice Parker on 4/8/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
public extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.height)
}
func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.width)
}
}

View File

@@ -0,0 +1,80 @@
//
// TickMarkSlider.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 11/8/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
public final class TickMarkSlider: UISlider {
private var enableFeedback = false
private let feedbackGenerator = UISelectionFeedbackGenerator()
private var roundedValue: Float?
public override var value: Float {
didSet {
let testValue = value.rounded()
if testValue != roundedValue && enableFeedback && value.truncatingRemainder(dividingBy: 1) == 0 {
roundedValue = testValue
feedbackGenerator.selectionChanged()
}
}
}
public func addTickMarks(color: UIColor) {
enableFeedback = true
let numberOfGaps = Int(maximumValue) - Int(minimumValue)
var gapLayoutGuides = [UILayoutGuide]()
for i in 0...numberOfGaps {
let tick = UIView()
tick.translatesAutoresizingMaskIntoConstraints = false
tick.backgroundColor = backgroundColor
insertSubview(tick, at: 0)
tick.widthAnchor.constraint(equalToConstant: 3).isActive = true
tick.heightAnchor.constraint(equalToConstant: 10).isActive = true
tick.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
if i == 0 {
tick.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
}
if let lastGapLayoutGuild = gapLayoutGuides.last {
lastGapLayoutGuild.trailingAnchor.constraint(equalTo: tick.leadingAnchor).isActive = true
}
if i != numberOfGaps {
let gapLayoutGuild = UILayoutGuide()
gapLayoutGuides.append(gapLayoutGuild)
addLayoutGuide(gapLayoutGuild)
tick.trailingAnchor.constraint(equalTo: gapLayoutGuild.leadingAnchor).isActive = true
} else {
tick.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
}
}
if let firstGapLayoutGuild = gapLayoutGuides.first {
for i in 1..<gapLayoutGuides.count {
gapLayoutGuides[i].widthAnchor.constraint(equalTo: firstGapLayoutGuild.widthAnchor).isActive = true
}
}
}
public override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let result = super.continueTracking(touch, with: event)
value = value.rounded()
return result
}
public override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
value = value.rounded()
}
}

View File

@@ -0,0 +1,35 @@
//
// UIResponder+.swift
// RSCore
//
// Created by Maurice Parker on 11/17/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
#if os(iOS)
import UIKit
extension UIResponder {
private weak static var _currentFirstResponder: UIResponder? = nil
public static var isFirstResponderTextField: Bool {
var isTextField = false
if let firstResponder = UIResponder.currentFirstResponder {
isTextField = firstResponder.isKind(of: UITextField.self) || firstResponder.isKind(of: UITextView.self) || firstResponder.isKind(of: UISearchBar.self)
}
return isTextField
}
public static var currentFirstResponder: UIResponder? {
UIResponder._currentFirstResponder = nil
UIApplication.shared.sendAction(#selector(findFirstResponder(sender:)), to: nil, from: nil, for: nil)
return UIResponder._currentFirstResponder
}
@objc internal func findFirstResponder(sender: AnyObject) {
UIResponder._currentFirstResponder = self
}
}
#endif

View File

@@ -0,0 +1,44 @@
//
// UITableView-Extensions.swift
// RSCoreiOS
//
// Created by Maurice Parker on 9/6/19.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
//
import UIKit
extension UITableView {
/**
Selects a row and scrolls it to the middle if it is not visible
*/
public func selectRowAndScrollIfNotVisible(at indexPath: IndexPath, animations: Animations) {
guard let dataSource = dataSource,
let numberOfSections = dataSource.numberOfSections,
indexPath.section < numberOfSections(self),
indexPath.row < dataSource.tableView(self, numberOfRowsInSection: indexPath.section) else {
return
}
selectRow(at: indexPath, animated: animations.contains(.select), scrollPosition: .none)
if let visibleIndexPaths = indexPathsForRows(in: safeAreaLayoutGuide.layoutFrame) {
if !(visibleIndexPaths.contains(indexPath) && cellCompletelyVisible(indexPath)) {
scrollToRow(at: indexPath, at: .middle, animated: animations.contains(.scroll))
}
}
}
func cellCompletelyVisible(_ indexPath: IndexPath) -> Bool {
let rect = rectForRow(at: indexPath)
return safeAreaLayoutGuide.layoutFrame.contains(rect)
}
public func middleVisibleRow() -> IndexPath? {
if let visibleIndexPaths = indexPathsForRows(in: safeAreaLayoutGuide.layoutFrame), visibleIndexPaths.count > 2 {
return visibleIndexPaths[visibleIndexPaths.count / 2]
}
return nil
}
}

View File

@@ -0,0 +1,28 @@
//
// UIView-Extensions.swift
// RSCore
//
// Created by Maurice Parker on 4/20/19.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
//
#if os(iOS)
import UIKit
extension UIView {
public func setFrameIfNotEqual(_ rect: CGRect) {
if !self.frame.equalTo(rect) {
self.frame = rect
}
}
public func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
#endif

View File

@@ -0,0 +1,28 @@
//
// UIViewController-Extensions.swift
// NetNewsWire
//
// Created by Maurice Parker on 4/15/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
#if os(iOS)
import UIKit
import SwiftUI
extension UIViewController {
// MARK: Error Handling
public func presentError(title: String, message: String, dismiss: (() -> Void)? = nil) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let dismissTitle = NSLocalizedString("OK", comment: "OK")
let dismissAction = UIAlertAction(title: dismissTitle, style: .default) { _ in
dismiss?()
}
alertController.addAction(dismissAction)
self.present(alertController, animated: true, completion: nil)
}
}
#endif