mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Move local modules into a folder named Modules.
This commit is contained in:
27
Modules/UIKitExtras/Sources/UIKitExtras/Animations.swift
Normal file
27
Modules/UIKitExtras/Sources/UIKitExtras/Animations.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
80
Modules/UIKitExtras/Sources/UIKitExtras/TickMarkSlider.swift
Normal file
80
Modules/UIKitExtras/Sources/UIKitExtras/TickMarkSlider.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
28
Modules/UIKitExtras/Sources/UIKitExtras/UIView+RSCore.swift
Normal file
28
Modules/UIKitExtras/Sources/UIKitExtras/UIView+RSCore.swift
Normal 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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user