├── .gitignore ├── Classes ├── Controllers │ ├── AlertController.swift │ ├── BaseApp │ │ ├── BaseApp+MainScene.swift │ │ ├── BaseApp+Scene.swift │ │ ├── BaseApp+Shortcut.swift │ │ ├── BaseApp+Shortcuts.swift │ │ ├── BaseApp.swift │ │ ├── LifeCycle.swift │ │ ├── SceneScreenType.swift │ │ ├── _Scene.swift │ │ └── _SceneDelegate.swift │ ├── Exports.swift │ ├── FormViewController.swift │ ├── MacApp.swift │ ├── MacOS+NavigationController.swift │ ├── MediaPicker.swift │ ├── Menu.swift │ ├── MenuItem.swift │ ├── NavigationController.swift │ ├── NotImplementedViewController.swift │ ├── StatusItem.swift │ ├── TabViewController.swift │ ├── ViewController.swift │ └── Window.swift ├── Enums │ ├── ContentInsetAdjustment.swift │ ├── DeclarativeConstraintAnySide.swift │ ├── DeclarativeConstraintCSide.swift │ ├── DeclarativeConstraintDSide.swift │ ├── DeclarativeConstraintXSide.swift │ ├── DeclarativeConstraintYSide.swift │ ├── Language.swift │ ├── LocalizedString.swift │ ├── MacOS+TextFieldContentType.swift │ ├── NavigationControllerStyle.swift │ ├── PushNotificationOption.swift │ ├── PushNotificationsAuthorizationStatus.swift │ ├── RootTransitionAnimation.swift │ ├── SegmentControlableItem.swift │ ├── StatusBarStyle.swift │ ├── TextFieldContentType.swift │ └── TextItemInteraction.swift ├── Extensions │ ├── Array+Diff.swift │ ├── AttrStr+Concat.swift │ ├── AttrStr+Joined.swift │ ├── ClosedRange+NSRange.swift │ ├── CollectionView+Cellable.swift │ ├── ConstraintValueType+Operators.swift │ ├── DeclarativeProtocol+Alpha.swift │ ├── DeclarativeProtocol+Apply.swift │ ├── DeclarativeProtocol+Background.swift │ ├── DeclarativeProtocol+Borders.swift │ ├── DeclarativeProtocol+Bounds.swift │ ├── DeclarativeProtocol+Click.swift │ ├── DeclarativeProtocol+CompressionResistance.swift │ ├── DeclarativeProtocol+ConstraintLinks.swift │ ├── DeclarativeProtocol+Constraints.swift │ ├── DeclarativeProtocol+ConstraintsRelative.swift │ ├── DeclarativeProtocol+ConstraintsSolo.swift │ ├── DeclarativeProtocol+ConstraintsSuper.swift │ ├── DeclarativeProtocol+Corners.swift │ ├── DeclarativeProtocol+DeclarativeView.swift │ ├── DeclarativeProtocol+Gesture.swift │ ├── DeclarativeProtocol+Hidden.swift │ ├── DeclarativeProtocol+Hover.swift │ ├── DeclarativeProtocol+HuggingPriority.swift │ ├── DeclarativeProtocol+Itself.swift │ ├── DeclarativeProtocol+LayoutMargin.swift │ ├── DeclarativeProtocol+LayoutSubviews.swift │ ├── DeclarativeProtocol+LongPress.swift │ ├── DeclarativeProtocol+Magnification.swift │ ├── DeclarativeProtocol+MovedToSuperview.swift │ ├── DeclarativeProtocol+NextResponder.swift │ ├── DeclarativeProtocol+Opacity.swift │ ├── DeclarativeProtocol+Pan.swift │ ├── DeclarativeProtocol+Pinch.swift │ ├── DeclarativeProtocol+Press.swift │ ├── DeclarativeProtocol+Rasterize.swift │ ├── DeclarativeProtocol+Rotation.swift │ ├── DeclarativeProtocol+ScreenEdgePan.swift │ ├── DeclarativeProtocol+Shadow.swift │ ├── DeclarativeProtocol+Swipe.swift │ ├── DeclarativeProtocol+Tag.swift │ ├── DeclarativeProtocol+Tap.swift │ ├── DeclarativeProtocol+Tint.swift │ ├── DeclarativeProtocol+UserInteraction.swift │ ├── NSEvent+Key.swift │ ├── NSLayoutConstraint+Activated.swift │ ├── NSLayoutConstraint+AllAttributes.swift │ ├── NSLayoutConstraint+Update.swift │ ├── NSSound+System.swift │ ├── NSView+BringToFront.swift │ ├── NavigationController+FadeTo.swift │ ├── NotificationCenter+Name.swift │ ├── Number+ConstraintValue.swift │ ├── Numeric+Device.swift │ ├── String+AttributedString.swift │ ├── String+LocalizedString.swift │ ├── String+SegmentControlable.swift │ ├── TableView+Cellable.swift │ ├── UIColor+Aplha.swift │ ├── UIColor+Dynamic.swift │ ├── UIColor+Hex.swift │ ├── UIControl+ActionHandler.swift │ ├── UIDevice+Model.swift │ ├── UIDeviceOrientation+Description.swift │ ├── UIFont+Family.swift │ ├── UIFont+PrintAll.swift │ ├── UIGestureRecognizer+GestureRecognizerable.swift │ ├── UIImage+Blur.swift │ ├── UIImage+Resize.swift │ ├── UIImage+SegmentControlable.swift │ ├── UIInterfaceOrientation+Description.swift │ ├── UIViewController+Body.swift │ ├── UIVisualEffect+Effects.swift │ ├── UNAuthorizationStatus+Status.swift │ ├── View+Add.swift │ ├── View+Body.swift │ ├── View+Menuable.swift │ ├── View+SafeArea.swift │ ├── View+Shake.swift │ ├── View+ViewWithTag.swift │ └── WrappedViewControllerable+Hidden.swift ├── Objects │ ├── Animation.swift │ ├── AttributedString.swift │ ├── ClickGestureRecognizer.swift │ ├── ForEach.swift │ ├── GestureDelegator.swift │ ├── GestureTracker.swift │ ├── HoverGestureRecognizer.swift │ ├── ImageLoader.swift │ ├── ImpactFeedback.swift │ ├── Localization.swift │ ├── LongPressGestureRecognizer.swift │ ├── MagnificationGestureRecognizer.swift │ ├── PanGestureRecognizer.swift │ ├── ParagraphStyle.swift │ ├── PinchGestureRecognizer.swift │ ├── PreConstraint.swift │ ├── PressGestureRecognizer.swift │ ├── Properties.swift │ ├── PropertiesInternal.swift │ ├── RotationGestureRecognizer.swift │ ├── ScreenEdgePanGestureRecognizer.swift │ ├── SwipeGestureRecognizer.swift │ ├── TapGestureRecognizer.swift │ ├── TextViewDelegate.swift │ └── UIViewPropertyAnimator.swift ├── Protocols │ ├── Alternateable.swift │ ├── AnyScene.swift │ ├── ArrowPositionable.swift │ ├── BackgroundColorable.swift │ ├── BezelStyleable.swift │ ├── Bezeledable.swift │ ├── BodyBuilderItem.swift │ ├── Borderedable.swift │ ├── BulletsEchoable.swift │ ├── Cellable.swift │ ├── Cleanupable.swift │ ├── Colorable.swift │ ├── ConstraintValue.swift │ ├── Contextable.swift │ ├── Continuousable.swift │ ├── ControlStateable.swift │ ├── DeclarativeProtocol.swift │ ├── DeclarativeProtocolInternal.swift │ ├── EditableStackView.swift │ ├── Editableable.swift │ ├── Enableable.swift │ ├── FirstResponderRefusable.swift │ ├── FocusRingTypeable.swift │ ├── Fontable.swift │ ├── GestureDelegatorable.swift │ ├── GestureRecognizerable.swift │ ├── GestureTrackable.swift │ ├── Hiddenable.swift │ ├── Identable.swift │ ├── KeyMaskable.swift │ ├── Keyable.swift │ ├── Keyboardable.swift │ ├── Menuable.swift │ ├── Messageable.swift │ ├── MixedStateAllowable.swift │ ├── MultiClickIgnorable.swift │ ├── NavigationControllerable.swift │ ├── Placeholderable.swift │ ├── PullsDownable.swift │ ├── Refreshable.swift │ ├── Scrollable.swift │ ├── Secureable.swift │ ├── SegmentControlable.swift │ ├── SideViewProtocol.swift │ ├── Soundable.swift │ ├── StackForEach.swift │ ├── SwiftUIable.swift │ ├── Taggable.swift │ ├── TextAdjustsFontSizeable.swift │ ├── TextAligmentable.swift │ ├── TextAttributesEditingAllowable.swift │ ├── TextAutocapitalizationable.swift │ ├── TextAutocorrectionable.swift │ ├── TextBindable.swift │ ├── TextFieldContentTypeable.swift │ ├── TextFieldDelegate.swift │ ├── TextFieldLeftViewable.swift │ ├── TextFieldRightViewable.swift │ ├── TextLineBreakModeable.swift │ ├── TextLineable.swift │ ├── TextScaleable.swift │ ├── Textable.swift │ ├── Tintable.swift │ ├── Titleable.swift │ ├── Typeable.swift │ ├── UIButtonable.swift │ ├── UICollectionViewable.swift │ ├── UILabelable.swift │ ├── UIScrollViewable.swift │ ├── UITableViewable.swift │ ├── UIViewable.swift │ ├── ViewTransitionable.swift │ └── WrappedViewControllerable.swift ├── Structs │ ├── AppBuilder.swift │ ├── BodyBuilder.swift │ ├── BodyBuilderItems.swift │ ├── Borders.swift │ ├── CodableState.swift │ ├── ConstraintValueType.swift │ ├── CustomCorners.swift │ ├── DeclarativeViewConstraints.swift │ ├── ExpressableState.swift │ ├── GesturesBuilder.swift │ ├── ImageReloadingStyle.swift │ ├── InnerState.swift │ ├── LivePreview.swift │ ├── PreviewBuilder.swift │ ├── PreviewBuilderItem.swift │ ├── ShortcutBuilder.swift │ ├── Spoken.swift │ ├── State.swift │ ├── StateStringBuilder.swift │ ├── UILayoutPriority.swift │ └── ViewContext.swift ├── Styles │ └── TextFieldStyle.swift └── Views │ ├── MacOS │ ├── MacOS+Button.swift │ ├── MacOS+ImageView.swift │ ├── MacOS+ScrollView.swift │ ├── MacOS+Text.swift │ ├── MacOS+TextField.swift │ ├── MacOS+VisualEffectView.swift │ ├── PopupButton.swift │ └── SecureTextField.swift │ ├── Not-MacOS │ ├── Button.swift │ ├── Collection.swift │ ├── CollectionDynamicCell.swift │ ├── CollectionView.swift │ ├── CollectionViewAlignedFlowLayout.swift │ ├── CollectionViewCell.swift │ ├── CollectionViewFlowLayout.swift │ ├── ControlView.swift │ ├── DatePickerView.swift │ ├── DynamicPickerView.swift │ ├── ImageView.swift │ ├── InputView.swift │ ├── LayerView.swift │ ├── List.swift │ ├── ListDynamicCell.swift │ ├── PickerView.swift │ ├── RefreshControl.swift │ ├── ScrollView.swift │ ├── SegmentedControl.swift │ ├── SliderView.swift │ ├── StaticListCell.swift │ ├── Stepper.swift │ ├── TableView.swift │ ├── TableViewCell.swift │ ├── Text.swift │ ├── TextField.swift │ ├── TextView.swift │ ├── Toggle.swift │ ├── VerificationCodeView.swift │ ├── VisualEffectView.swift │ └── WrappedViewControllerView.swift │ └── Universal │ ├── ActivityIndicator.swift │ ├── BarButtonItemView.swift │ ├── BaseView.swift │ ├── HScrollStack.swift │ ├── HSpace.swift │ ├── HStack.swift │ ├── HUD.swift │ ├── Space.swift │ ├── StackView.swift │ ├── VScrollStack.swift │ ├── VSpace.swift │ ├── VStack.swift │ ├── View.swift │ └── WrapperView.swift ├── Docs └── Gestures.md ├── LICENSE ├── Package.swift ├── README.md ├── Templates └── Project Templates │ └── Application │ └── UIKitPlus App.xctemplate │ ├── App.swift │ ├── Extensions │ ├── Buttons.swift │ ├── Colors.swift │ ├── Fonts.swift │ └── Images.swift │ ├── Images.xcassets │ └── Contents.json │ ├── LaunchScreen.storyboard │ ├── TemplateIcon.png │ ├── TemplateIcon@2x.png │ ├── TemplateInfo.plist │ └── ViewControllers │ ├── MainViewController.swift │ ├── SplashViewController.swift │ └── WelcomeViewController.swift └── UIKit-Plus.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | .build 7 | .swiftpm 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata/ 17 | *.xccheckout 18 | *.xcodeproj 19 | profile 20 | *.moved-aside 21 | DerivedData 22 | *.hmap 23 | *.ipa 24 | TestProject 25 | 26 | # Bundler 27 | .bundle 28 | 29 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 30 | # Carthage/Checkouts 31 | 32 | Carthage/Build 33 | 34 | # We recommend against adding the Pods directory to your .gitignore. However 35 | # you should judge for yourself, the pros and cons are mentioned at: 36 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 37 | # 38 | # Note: if you ignore the Pods directory, make sure to uncomment 39 | # `pod install` in .travis.yml 40 | # 41 | Example/Pods/ 42 | -------------------------------------------------------------------------------- /Classes/Controllers/BaseApp/BaseApp+Scene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseApp+Scene.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 11.09.2020. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | extension BaseApp { 12 | public class Scene: _AnyScene, AppBuilderContent { 13 | public var appBuilderContent: AppBuilderItem { .scene(self) } 14 | 15 | public var persistentIdentifier: String = UUID().uuidString 16 | public var stateRestorationActivity: NSUserActivity? 17 | public var userInfo: [String : Any]? 18 | 19 | var _onConnect: ((UIWindow?) -> Void)? 20 | var _onDisconnect: ((UIWindow?) -> Void)? 21 | var _onDestroy: ((UIWindow?) -> Void)? 22 | var _onBecomeActive: ((UIWindow?) -> Void)? 23 | var _onWillResignActive: ((UIWindow?) -> Void)? 24 | var _onWillEnterForeground: ((UIWindow?) -> Void)? 25 | var _onDidEnterBackground: ((UIWindow?) -> Void)? 26 | } 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /Classes/Controllers/BaseApp/BaseApp+Shortcuts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseApp+Shortcuts.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 11.09.2020. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | extension BaseApp { 12 | public class Shortcuts: AppBuilderContent { 13 | public var appBuilderContent: AppBuilderItem { .shortcuts(self) } 14 | 15 | var shortcuts: [Shortcut] = [] 16 | 17 | public init (@ShortcutBuilder block: ShortcutBuilder.Block) { 18 | parseShortcutBuilderItem(block().shortcutBuilderContent) 19 | } 20 | 21 | private func parseShortcutBuilderItem(_ item: ShortcutBuilderItem) { 22 | switch item { 23 | case .shortcut(let v): shortcuts.append(v) 24 | case .items(let items): items.forEach { parseShortcutBuilderItem($0) } 25 | case .none: break 26 | } 27 | } 28 | 29 | func onChange() -> Self { 30 | 31 | return self 32 | } 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /Classes/Controllers/BaseApp/SceneScreenType.swift: -------------------------------------------------------------------------------- 1 | public struct SceneScreenType: ExpressibleByStringLiteral, CustomStringConvertible, Equatable, Hashable { 2 | public let type: String 3 | 4 | init (type: String) { 5 | self.type = type 6 | } 7 | 8 | public init(stringLiteral value: String) { 9 | self.init(type: value) 10 | } 11 | 12 | public static var splash: Self { "splash" } 13 | public static var login: Self { "login" } 14 | public static var logout: Self { "logout" } 15 | public static var main: Self { "main" } 16 | public static var onboarding: Self { "onboarding" } 17 | 18 | public var description: String { type } 19 | 20 | public static func == (lhs: Self, rhs: Self) -> Bool { 21 | lhs.type == rhs.type 22 | } 23 | 24 | public func hash(into hasher: inout Hasher) { 25 | hasher.combine(type) 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /Classes/Controllers/BaseApp/_Scene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // _Scene.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 11.09.2020. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | @available(iOS 13.0, *) 12 | class _Scene: UIWindowScene {} 13 | #endif 14 | -------------------------------------------------------------------------------- /Classes/Controllers/Exports.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | @_exported import AppKit 3 | #else 4 | @_exported import UIKit 5 | #endif 6 | -------------------------------------------------------------------------------- /Classes/Controllers/FormViewController.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | open class FormViewController: ViewController { 5 | public lazy var scrollView = UScrollView() 6 | public lazy var stackView = UVStack() 7 | 8 | open override func buildUI() { 9 | super.buildUI() 10 | view.body { 11 | scrollView 12 | .edgesToSuperview(top: 0, leading: 0, trailing: 0) 13 | .bottomToSuperview($keyboardHeight.map { -1 * $0 }) 14 | } 15 | scrollView.body { 16 | stackView 17 | .edgesToSuperview() 18 | .width(to: .width, of: scrollView) 19 | } 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Classes/Controllers/NotImplementedViewController.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | class NotImplementedViewController: ViewController { 8 | #if !os(macOS) 9 | override var statusBarStyle: StatusBarStyle { .light } 10 | #endif 11 | 12 | let name: String 13 | 14 | init (_ name: String) { 15 | self.name = name 16 | #if os(macOS) 17 | super.init(nibName: nil, bundle: nil) 18 | #else 19 | super.init() 20 | #endif 21 | } 22 | 23 | public required init?(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | override func buildUI() { 28 | super.buildUI() 29 | #if os(macOS) 30 | 31 | #else 32 | view.backgroundColor = .darkGray 33 | body { 34 | UText("`\(name)`".foreground(.red), " hasn't been implemented yet") 35 | .color(.white) 36 | .alignment(.center) 37 | .centerYInSuperview() 38 | .edgesToSuperview(leading: 0, trailing: 0) 39 | } 40 | #endif 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Classes/Controllers/TabViewController.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | open class TabViewController: UITabBarController { 5 | @discardableResult 6 | public func title(_ title: String) -> TabViewController { 7 | navigationItem.title = title 8 | tabBarItem.title = title 9 | return self 10 | } 11 | 12 | @discardableResult 13 | public func title(_ header: String, _ item: String) -> TabViewController { 14 | navigationItem.title = header 15 | tabBarItem.title = item 16 | return self 17 | } 18 | 19 | @discardableResult 20 | public func icon(_ image: _UImage?) -> TabViewController { 21 | tabBarItem.image = image 22 | return self 23 | } 24 | 25 | @discardableResult 26 | public func icon(_ named: String) -> TabViewController { 27 | tabBarItem.image = UIImage(named: named) 28 | return self 29 | } 30 | 31 | @discardableResult 32 | public func tint(_ color: UColor) -> TabViewController { 33 | tabBarController?.tabBar.tintColor = color 34 | return self 35 | } 36 | 37 | @discardableResult 38 | public func tint(_ numberColor: Int) -> TabViewController { 39 | tabBarController?.tabBar.tintColor = numberColor.color 40 | return self 41 | } 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /Classes/Enums/ContentInsetAdjustment.swift: -------------------------------------------------------------------------------- 1 | public enum ContentInsetAdjustment: Int { 2 | case automatic 3 | case scrollableAxes 4 | case never 5 | case always 6 | } 7 | -------------------------------------------------------------------------------- /Classes/Enums/DeclarativeConstraintAnySide.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | enum DeclarativeConstraintAnySide { 8 | case cx(DeclarativeConstraintCXSide) 9 | case cy(DeclarativeConstraintCYSide) 10 | case d(DeclarativeConstraintDSide) 11 | case x(DeclarativeConstraintXSide) 12 | case y(DeclarativeConstraintYSide) 13 | 14 | var attribute: NSLayoutConstraint.Attribute { 15 | switch self { 16 | case .cx(let side): return side.side 17 | case .cy(let side): return side.side 18 | case .d(let side): return side.side 19 | case .x(let side): return side.side 20 | case .y(let side): return side.side 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Classes/Enums/DeclarativeConstraintDSide.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public enum DeclarativeConstraintDSide: Int { 8 | case width, height 9 | var side: NSLayoutConstraint.Attribute { 10 | switch self { 11 | case .width: return .width 12 | case .height: return .height 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Classes/Enums/DeclarativeConstraintXSide.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public enum DeclarativeConstraintXSide: Int { 8 | case leading, leadingMargin, left, leftMargin, trailing, trailingMargin, right, rightMargin, centerX, centerXMargin 9 | var side: NSLayoutConstraint.Attribute { 10 | switch self { 11 | case .leading: return .leading 12 | case .leadingMargin: 13 | #if os(macOS) 14 | return .leading 15 | #else 16 | return .leadingMargin 17 | #endif 18 | case .left: return .left 19 | case .leftMargin: 20 | #if os(macOS) 21 | return .left 22 | #else 23 | return .leftMargin 24 | #endif 25 | case .trailing: return .trailing 26 | case .trailingMargin: 27 | #if os(macOS) 28 | return .trailing 29 | #else 30 | return .trailingMargin 31 | #endif 32 | case .right: return .right 33 | case .rightMargin: 34 | #if os(macOS) 35 | return .right 36 | #else 37 | return .rightMargin 38 | #endif 39 | case .centerX: return .centerX 40 | case .centerXMargin: 41 | #if os(macOS) 42 | return .centerX 43 | #else 44 | return .centerXWithinMargins 45 | #endif 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Classes/Enums/DeclarativeConstraintYSide.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public enum DeclarativeConstraintYSide: Int { 8 | case top, topMargin, bottom, bottomMargin 9 | var side: NSLayoutConstraint.Attribute { 10 | switch self { 11 | case .top: return .top 12 | case .topMargin: 13 | #if os(macOS) 14 | return .top 15 | #else 16 | return .topMargin 17 | #endif 18 | case .bottom: return .bottom 19 | case .bottomMargin: 20 | #if os(macOS) 21 | return .bottom 22 | #else 23 | return .bottomMargin 24 | #endif 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Classes/Enums/MacOS+TextFieldContentType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MacOS+TextContentType.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 27.08.2020. 6 | // 7 | 8 | #if os(macOS) 9 | import AppKit 10 | 11 | public enum TextFieldContentType { 12 | case username, password, oneTimeCode 13 | 14 | #if swift(>=5.3) 15 | @available(OSX 10.16, *) 16 | var nsType: NSTextContentType { 17 | switch self { 18 | case .username: return .username 19 | case .password: return .password 20 | case .oneTimeCode: return .oneTimeCode 21 | } 22 | } 23 | #endif 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /Classes/Enums/NavigationControllerStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public enum NavigationControllerStyle { 5 | case `default`, transparent 6 | case color(UColor) 7 | } 8 | #endif 9 | -------------------------------------------------------------------------------- /Classes/Enums/PushNotificationOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushNotificationOption.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 10.09.2020. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public enum PushNotificationOption { 12 | case badge, sound, alert, carPlay 13 | // iOS12+ 14 | case criticalAlert, providesAppNotificationSettings, provisional 15 | // iOS13+ 16 | case announcement 17 | 18 | @available(iOS 10.0, *) 19 | var unOption: UNAuthorizationOptions? { 20 | switch self { 21 | case .badge: return .badge 22 | case .sound: return .sound 23 | case .alert: return .alert 24 | case .carPlay: return .carPlay 25 | case .criticalAlert: 26 | if #available(iOS 12.0, *) { 27 | return .criticalAlert 28 | } 29 | return nil 30 | case .providesAppNotificationSettings: 31 | if #available(iOS 12.0, *) { 32 | return .providesAppNotificationSettings 33 | } 34 | return nil 35 | case .provisional: 36 | if #available(iOS 12.0, *) { 37 | return .provisional 38 | } 39 | return nil 40 | case .announcement: 41 | if #available(iOS 13.0, *) { 42 | return .announcement 43 | } 44 | return nil 45 | } 46 | } 47 | 48 | var oldType: UIUserNotificationType? { 49 | switch self { 50 | case .badge: return .badge 51 | case .sound: return .sound 52 | case .alert: return .alert 53 | case .carPlay: return nil 54 | case .criticalAlert: return nil 55 | case .providesAppNotificationSettings: return nil 56 | case .provisional: return nil 57 | case .announcement: return nil 58 | } 59 | } 60 | } 61 | 62 | extension Array where Element == PushNotificationOption { 63 | @available(iOS 10.0, *) 64 | var unOption: UNAuthorizationOptions { 65 | .init(compactMap { $0.unOption }) 66 | } 67 | 68 | var oldSettings: UIUserNotificationSettings { 69 | .init(types: .init(compactMap { $0.oldType }), categories: nil) 70 | } 71 | } 72 | #endif 73 | -------------------------------------------------------------------------------- /Classes/Enums/PushNotificationsAuthorizationStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushNotificationsAuthorizationStatus.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 10.09.2020. 6 | // 7 | 8 | public enum PushNotificationsAuthorizationStatus { 9 | // The user has not yet made a choice regarding whether the application may post user notifications. 10 | case notDetermined 11 | 12 | // The application is not authorized to post user notifications. 13 | case denied 14 | 15 | // The application is authorized to post user notifications. 16 | case authorized 17 | 18 | // The application is authorized to post non-interruptive user notifications. 19 | case provisional 20 | } 21 | -------------------------------------------------------------------------------- /Classes/Enums/RootTransitionAnimation.swift: -------------------------------------------------------------------------------- 1 | public enum RootTransitionAnimation { 2 | case none, dismiss, fade 3 | } 4 | -------------------------------------------------------------------------------- /Classes/Enums/SegmentControlableItem.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public enum SegmentControlableItem { 8 | case title(String) 9 | case image(_UImage) 10 | } 11 | -------------------------------------------------------------------------------- /Classes/Enums/StatusBarStyle.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public enum StatusBarStyle { 5 | /// Automatic color. Depends on current iOS theme. 6 | case `default` 7 | /// Automatic color. Depends on current iOS theme but inverted. 8 | case defaultInverted 9 | /// White color 10 | case light 11 | /// Black color 12 | case dark 13 | #if !os(tvOS) 14 | public var rawValue: UIStatusBarStyle { 15 | switch self { 16 | case .default: 17 | return .default 18 | case .defaultInverted: 19 | if #available(iOS 13.0, *) { 20 | if UITraitCollection.current.userInterfaceStyle == .dark { 21 | return .darkContent 22 | } 23 | else { 24 | return .lightContent 25 | } 26 | } 27 | return .default 28 | case .light: 29 | return .lightContent 30 | case .dark: 31 | if #available(iOS 13.0, *) { 32 | return .darkContent 33 | } else { 34 | return .default 35 | } 36 | } 37 | } 38 | 39 | public static func from(_ style: UIStatusBarStyle) -> StatusBarStyle { 40 | switch style { 41 | case .default: return .default 42 | case .lightContent: return .light 43 | case .darkContent: return .dark 44 | @unknown default: return .default 45 | } 46 | } 47 | #endif 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /Classes/Enums/TextItemInteraction.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public enum TextItemInteraction : Int { 5 | case invokeDefaultAction, presentActions, preview 6 | 7 | @available(iOS 10.0, *) 8 | static func from(_ item: UITextItemInteraction) -> TextItemInteraction { 9 | switch item { 10 | case .invokeDefaultAction: return .invokeDefaultAction 11 | case .presentActions: return .presentActions 12 | case .preview: return .preview 13 | @unknown default: 14 | assertionFailure("TextItemInteraction case not supported") 15 | return .invokeDefaultAction 16 | } 17 | } 18 | } 19 | #endif 20 | -------------------------------------------------------------------------------- /Classes/Extensions/AttrStr+Concat.swift: -------------------------------------------------------------------------------- 1 | public func +(lhs: AttrStr, rhs: AttrStr) -> AttrStr { 2 | let attrStr = AttrStr("") 3 | attrStr._attributedString.append(lhs._attributedString) 4 | attrStr._attributedString.append(rhs._attributedString) 5 | return attrStr 6 | } 7 | -------------------------------------------------------------------------------- /Classes/Extensions/AttrStr+Joined.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AttrStr+Joined.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 09.02.2020. 6 | // 7 | 8 | import Foundation 9 | #if os(macOS) 10 | import AppKit 11 | #else 12 | import UIKit 13 | #endif 14 | 15 | extension Array: AnyString where Element == AnyString { 16 | public var attributedString: NSAttributedString { 17 | var res: NSMutableAttributedString = .init() 18 | for str in self { 19 | res.append(str.attributedString) 20 | } 21 | return res 22 | } 23 | 24 | public func onUpdate(_ handler: @escaping (NSAttributedString) -> Void) { 25 | for str in self { 26 | str.onUpdate { 27 | handler($0.attributedString) 28 | } 29 | } 30 | } 31 | 32 | public static func make(_ v: NSAttributedString) -> Self { 33 | .init([v]) 34 | } 35 | } 36 | 37 | extension Array where Element: NSAttributedString { 38 | public func joined() -> NSAttributedString { 39 | let result = NSMutableAttributedString(string: "") 40 | for str in self { 41 | result.append(str) 42 | } 43 | return result 44 | } 45 | } 46 | 47 | extension Array where Element: AttributedString { 48 | public func joined() -> AttrStr { 49 | let result = AttributedString("") 50 | for str in self { 51 | result._attributedString.append(str._attributedString) 52 | } 53 | return result 54 | } 55 | 56 | public func joined() -> NSMutableAttributedString { 57 | let result = NSMutableAttributedString(string: "") 58 | for str in self { 59 | result.append(str._attributedString) 60 | } 61 | return result 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Classes/Extensions/ClosedRange+NSRange.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension ClosedRange where Bound == Int { 4 | var nsRange: NSRange { return .init(location: first ?? 0, length: last ?? 0) } 5 | } 6 | -------------------------------------------------------------------------------- /Classes/Extensions/CollectionView+Cellable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import Foundation 3 | import UIKit 4 | 5 | extension UICollectionView { 6 | @discardableResult 7 | public func register(_ cellClass: Cellable.Type...) -> Self { 8 | cellClass.forEach { register($0, forCellWithReuseIdentifier: $0.reuseIdentifier) } 9 | return self 10 | } 11 | 12 | public func dequeueReusableCell(with class: T.Type, for indexPath: IndexPath) -> T { 13 | dequeueReusableCell(withReuseIdentifier: `class`.reuseIdentifier, for: indexPath) as! T 14 | } 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /Classes/Extensions/ConstraintValueType+Operators.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(macOS) 3 | import AppKit 4 | #else 5 | import UIKit 6 | #endif 7 | 8 | prefix operator >= 9 | public prefix func >=(rhs: CGFloat) -> ConstraintValueType { 10 | .init(.greaterThanOrEqual, rhs) 11 | } 12 | 13 | prefix operator >=- 14 | public prefix func >=-(rhs: CGFloat) -> ConstraintValueType { 15 | .init(.greaterThanOrEqual, -1 * rhs) 16 | } 17 | 18 | prefix operator <= 19 | public prefix func <=(rhs: CGFloat) -> ConstraintValueType { 20 | .init(.lessThanOrEqual, rhs) 21 | } 22 | 23 | prefix operator <=- 24 | public prefix func <=-(rhs: CGFloat) -> ConstraintValueType { 25 | .init(.lessThanOrEqual, -1 * rhs) 26 | } 27 | 28 | /// Operator for constraint + multiplier 29 | /// Example: 30 | /// ```swift 31 | ///view.left(to: view2, 350 ~ 1.5) 32 | ///``` 33 | /// `350 ~ 1.5` above means: 350pt with 1.5 multiplier 34 | infix operator ~ : AdditionPrecedence 35 | public func ~(lhs: ConstraintValue, rhs: CGFloat) -> ConstraintValue { 36 | ConstraintValueType(lhs.constraintValue.relation, lhs.constraintValue.value, rhs, lhs.constraintValue.priority) 37 | } 38 | 39 | 40 | /// Operator for constraint + layout-priority 41 | /// Example: 42 | /// ```swift 43 | ///view.left(to: view2, 350 ! 999) 44 | ///``` 45 | /// `350 ! 999` above means: 350pt with 999 priority 46 | infix operator ! : AdditionPrecedence 47 | public func !(lhs: ConstraintValue, rhs: UILayoutPriority) -> ConstraintValue { 48 | ConstraintValueType(lhs.constraintValue.relation, lhs.constraintValue.value, lhs.constraintValue.multiplier, rhs) 49 | } 50 | 51 | public func !(lhs: ConstraintValue, rhs: Float) -> ConstraintValue { 52 | ConstraintValueType(lhs.constraintValue.relation, lhs.constraintValue.value, lhs.constraintValue.multiplier, .init(rhs)) 53 | } 54 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Alpha.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension DeclarativeProtocol { 8 | public var alpha: State { properties.$alpha } 9 | 10 | @discardableResult 11 | public func alpha(_ alpha: CGFloat) -> Self { 12 | #if os(macOS) 13 | declarativeView.alphaValue = alpha 14 | #else 15 | declarativeView.alpha = alpha 16 | #endif 17 | properties.alpha = alpha 18 | return self 19 | } 20 | 21 | @discardableResult 22 | public func alpha(_ state: State) -> Self { 23 | alpha(state.wrappedValue) 24 | state.listen { [weak self] old, new in 25 | self?.alpha(new) 26 | } 27 | return self 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Apply.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Kevin Guo on 2022/1/3. 3 | // 4 | 5 | extension DeclarativeProtocol { 6 | /// Helper method where you could change the object non-declarative way 7 | /// 8 | /// ```swift 9 | /// view1.apply { 10 | /// $0.backgroundColor = .red 11 | /// } 12 | /// ``` 13 | @discardableResult 14 | public func apply(_ closure: (V) -> Void) -> Self { 15 | closure(declarativeView) 16 | return self 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Background.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension AnyDeclarativeProtocol { 8 | public var background: State { properties.$background } 9 | 10 | var _backgroundColorState: State { background } 11 | 12 | #if os(macOS) 13 | func _setBackgroundColor(_ v: NSColor?) { 14 | declarativeView.wantsLayer = true 15 | declarativeView.layer?.backgroundColor = v?.cgColor 16 | } 17 | #else 18 | func _setBackgroundColor(_ v: UColor?) { 19 | declarativeView.backgroundColor = v 20 | } 21 | #endif 22 | 23 | // @discardableResult 24 | // public func background(_ number: Int) -> Self { 25 | // background(number.color) 26 | // } 27 | // 28 | // @discardableResult 29 | // public func background(_ color: UColor) -> Self { 30 | // _setBackground(.init(wrappedValue: color)) 31 | // return self 32 | // } 33 | // 34 | // @discardableResult 35 | // public func background(_ state: State) -> Self { 36 | // _setBackground(state) 37 | // state.listen { [weak self] old, new in 38 | // self?.background(new) 39 | // self?.properties.background = new 40 | // } 41 | // return self 42 | // } 43 | // 44 | // private func _setBackground(_ color: State) { 45 | // #if os(macOS) 46 | // background.wrappedValue.changeHandler = nil 47 | // properties.background = color.wrappedValue 48 | // declarativeView.wantsLayer = true 49 | // declarativeView.layer?.backgroundColor = color.wrappedValue.current.cgColor 50 | // properties.background.onChange { [weak self] new in 51 | // self?.declarativeView.layer?.backgroundColor = new.cgColor 52 | // } 53 | // #else 54 | // properties.background = color.wrappedValue 55 | // declarativeView.backgroundColor = color 56 | // #endif 57 | // } 58 | } 59 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Bounds.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension DeclarativeProtocol { 8 | #if !os(macOS) 9 | @discardableResult 10 | public func clipsToBounds(_ value: Bool = true) -> Self { 11 | declarativeView.clipsToBounds = value 12 | return self 13 | } 14 | #endif 15 | 16 | @discardableResult 17 | public func masksToBounds(_ value: Bool = true) -> Self { 18 | #if os(macOS) 19 | declarativeView.layer?.masksToBounds = value 20 | #else 21 | declarativeView.layer.masksToBounds = value 22 | #endif 23 | return self 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+CompressionResistance.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension DeclarativeProtocol { 8 | @discardableResult 9 | public func compressionResistance(x value: UILayoutPriority) -> Self { 10 | #if !os(macOS) 11 | declarativeView.setContentCompressionResistancePriority(value, for: .horizontal) 12 | #endif 13 | return self 14 | } 15 | 16 | @discardableResult 17 | public func compressionResistance(y value: UILayoutPriority) -> Self { 18 | #if !os(macOS) 19 | declarativeView.setContentCompressionResistancePriority(value, for: .vertical) 20 | #endif 21 | return self 22 | } 23 | 24 | @discardableResult 25 | public func compressionResistance(x value: Float) -> Self { 26 | declarativeView.setContentCompressionResistancePriority(.init(value), for: .horizontal) 27 | return self 28 | } 29 | 30 | @discardableResult 31 | public func compressionResistance(y value: Float) -> Self { 32 | declarativeView.setContentCompressionResistancePriority(.init(value), for: .vertical) 33 | return self 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+ConstraintLinks.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension DeclarativeProtocol { 8 | // public var height: State { _declarativeView._properties.$height } 9 | // public var width: State { _declarativeView._properties.$width } 10 | // public var top: State { _declarativeView._properties.$top } 11 | // public var leading: State { _declarativeView._properties.$leading } 12 | // public var left: State { _declarativeView._properties.$left } 13 | // public var trailing: State { _declarativeView._properties.$trailing } 14 | // public var right: State { _declarativeView._properties.$right } 15 | // public var bottom: State { _declarativeView._properties.$bottom } 16 | // public var centerX: State { _declarativeView._properties.$centerX } 17 | // public var centerY: State { _declarativeView._properties.$centerY } 18 | } 19 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Corners.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | 4 | extension DeclarativeProtocol { 5 | @discardableResult 6 | public func circle() -> Self { 7 | _declarativeView._properties.circleCorners = true 8 | return self 9 | } 10 | 11 | @discardableResult 12 | public func corners(_ radius: CGFloat) -> Self { 13 | _declarativeView._properties.circleCorners = false 14 | declarativeView.wantsLayer = true 15 | declarativeView.layer?.cornerRadius = radius 16 | return self 17 | } 18 | 19 | @discardableResult 20 | public func corners(_ state: State) -> Self { 21 | corners(state.wrappedValue) 22 | state.listen { [weak self] old, new in 23 | self?.corners(state.wrappedValue) 24 | } 25 | return self 26 | } 27 | } 28 | #else 29 | import UIKit 30 | 31 | extension DeclarativeProtocol { 32 | @discardableResult 33 | public func circle() -> Self { 34 | _declarativeView._properties.circleCorners = true 35 | _declarativeView._properties.customCorners = nil 36 | return self 37 | } 38 | 39 | @discardableResult 40 | public func corners(_ radius: CGFloat, _ corners: [UIRectCorner]) -> Self { 41 | _declarativeView._properties.circleCorners = false 42 | _declarativeView._properties.customCorners = nil 43 | guard corners.count > 0 else { 44 | declarativeView.layer.cornerRadius = radius 45 | declarativeView.layoutIfNeeded() 46 | return self 47 | } 48 | _declarativeView._properties.customCorners = CustomCorners(radius: radius, corners: corners) 49 | return self 50 | } 51 | 52 | @discardableResult 53 | public func corners(_ radius: CGFloat, _ corners: UIRectCorner...) -> Self { 54 | self.corners(radius, corners) 55 | } 56 | 57 | @discardableResult 58 | public func corners(_ state: State) -> Self { 59 | corners(state.wrappedValue) 60 | state.listen { [weak self] new in 61 | self?.corners(new) 62 | } 63 | return self 64 | } 65 | } 66 | #endif 67 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+DeclarativeView.swift: -------------------------------------------------------------------------------- 1 | extension DeclarativeProtocol { 2 | internal var _declarativeView: DeclarativeProtocolInternal { self as! DeclarativeProtocolInternal } 3 | } 4 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Gesture.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension DeclarativeProtocol { 8 | @discardableResult 9 | public func gesture(_ recognizer: UGestureRecognizer) -> Self { 10 | gestures(recognizer) 11 | } 12 | 13 | @discardableResult 14 | public func gestures(_ recognizers: UGestureRecognizer...) -> Self { 15 | gestures(recognizers) 16 | } 17 | 18 | @discardableResult 19 | public func gestures(_ recognizers: [UGestureRecognizer]) -> Self { 20 | recognizers.forEach { declarativeView.addGestureRecognizer($0) } 21 | return self 22 | } 23 | 24 | @discardableResult 25 | public func gestures(@GesturesBuilder block: GesturesBuilder.Block) -> Self { 26 | gestures(block().gestureRecognizers) 27 | } 28 | 29 | @discardableResult 30 | public func gestures(@GesturesBuilder block: (Self) -> GesturesBuilderItem) -> Self { 31 | gestures(block(self).gestureRecognizers) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Hidden.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension DeclarativeProtocol { 8 | public var hidden: State { properties.$hidden } 9 | 10 | @discardableResult 11 | public func hidden(_ hidden: Bool = true) -> Self { 12 | declarativeView.isHidden = hidden 13 | properties.hidden = hidden 14 | return self 15 | } 16 | 17 | @discardableResult 18 | public func hidden(_ state: State) -> Self { 19 | hidden(state.wrappedValue) 20 | state.listen { [weak self] new in 21 | self?.hidden(new) 22 | } 23 | return self 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Hover.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | extension DeclarativeProtocol { 5 | #if !os(tvOS) 6 | @discardableResult 7 | public func onHoverGesture(_ action: @escaping (Self) -> Void) -> Self { 8 | onHoverGesture { v, s, r in 9 | action(v) 10 | } 11 | } 12 | 13 | @discardableResult 14 | public func onHoverGesture(_ action: @escaping (Self, UIGestureRecognizer.State) -> Void) -> Self { 15 | onHoverGesture { v, s, r in 16 | action(v, s) 17 | } 18 | } 19 | 20 | @discardableResult 21 | public func onHoverGesture(_ action: @escaping (Self, UIGestureRecognizer.State, UIGestureRecognizer) -> Void) -> Self { 22 | if #available(iOS 13.0, *) { 23 | let recognizer = HoverGestureRecognizer() 24 | declarativeView.addGestureRecognizer(recognizer.trackState { [weak self, weak recognizer] in 25 | guard let self = self, let recognizer = recognizer else { return } 26 | action(self, $0, recognizer) 27 | }) 28 | } 29 | return self 30 | } 31 | 32 | @discardableResult 33 | public func onHoverGesture(_ state: State) -> Self { 34 | onHoverGesture { v, s, r in 35 | state.wrappedValue = s 36 | } 37 | } 38 | 39 | @discardableResult 40 | public func hovered(_ action: @escaping (Bool) -> Void) -> Self { 41 | onHoverGesture { [weak self] v, s, r in 42 | let hovered = [.began, .changed].contains(s) 43 | guard hovered != self?.properties.hovered else { return } 44 | self?.properties.hovered = hovered 45 | action(hovered) 46 | } 47 | } 48 | 49 | @discardableResult 50 | public func hovered(_ state: State) -> Self { 51 | hovered { 52 | state.wrappedValue = $0 53 | } 54 | } 55 | #endif 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+HuggingPriority.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | extension DeclarativeProtocol { 5 | @discardableResult 6 | public func huggingPriority(x value: UILayoutPriority) -> Self { 7 | declarativeView.setContentHuggingPriority(value, for: .horizontal) 8 | return self 9 | } 10 | 11 | @discardableResult 12 | public func huggingPriority(y value: UILayoutPriority) -> Self { 13 | declarativeView.setContentHuggingPriority(value, for: .vertical) 14 | return self 15 | } 16 | 17 | @discardableResult 18 | public func huggingPriority(x value: Float) -> Self { 19 | declarativeView.setContentHuggingPriority(.init(value), for: .horizontal) 20 | return self 21 | } 22 | 23 | @discardableResult 24 | public func huggingPriority(y value: Float) -> Self { 25 | declarativeView.setContentHuggingPriority(.init(value), for: .vertical) 26 | return self 27 | } 28 | } 29 | #endif 30 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Itself.swift: -------------------------------------------------------------------------------- 1 | extension DeclarativeProtocol { 2 | public func itself(_ itself: inout Self?) -> Self { 3 | itself = self 4 | return self 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+LayoutMargin.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | extension DeclarativeProtocol { 5 | @discardableResult 6 | public func layoutMargin(top: CGFloat? = nil, left: CGFloat? = nil, right: CGFloat? = nil, bottom: CGFloat? = nil) -> Self { 7 | if let top = top { 8 | declarativeView.layoutMargins.top = top 9 | } 10 | if let left = left { 11 | declarativeView.layoutMargins.left = left 12 | } 13 | if let right = right { 14 | declarativeView.layoutMargins.right = right 15 | } 16 | if let bottom = bottom { 17 | declarativeView.layoutMargins.bottom = bottom 18 | } 19 | return self 20 | } 21 | 22 | @discardableResult 23 | public func layoutMargin(x: CGFloat? = nil, y: CGFloat? = nil) -> Self { 24 | if let y = y { 25 | declarativeView.layoutMargins.top = y 26 | declarativeView.layoutMargins.bottom = y 27 | } 28 | if let x = x { 29 | declarativeView.layoutMargins.left = x 30 | declarativeView.layoutMargins.right = x 31 | } 32 | return self 33 | } 34 | 35 | @discardableResult 36 | public func layoutMargin(_ value: CGFloat) -> Self { 37 | declarativeView.layoutMargins.top = value 38 | declarativeView.layoutMargins.bottom = value 39 | declarativeView.layoutMargins.left = value 40 | declarativeView.layoutMargins.right = value 41 | return self 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+LayoutSubviews.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | 4 | extension AnyDeclarativeProtocol { 5 | func onLayoutSubviews() { 6 | if _declarativeView._properties.circleCorners == true { 7 | if let minSide = [declarativeView.bounds.size.width, declarativeView.bounds.size.height].min() { 8 | declarativeView.layer?.cornerRadius = minSide / 2 9 | } 10 | } 11 | } 12 | } 13 | #else 14 | import UIKit 15 | 16 | extension AnyDeclarativeProtocol { 17 | func onLayoutSubviews() { 18 | if _declarativeView._properties.circleCorners == true { 19 | if let minSide = [declarativeView.bounds.size.width, declarativeView.bounds.size.height].min() { 20 | declarativeView.layer.cornerRadius = minSide / 2 21 | } 22 | } else if let customCorners = _declarativeView._properties.customCorners { 23 | if _declarativeView._properties.customCorners?.backgroundColor == nil { 24 | _declarativeView._properties.customCorners?.backgroundColor = declarativeView.backgroundColor == .clear ? .white : declarativeView.backgroundColor 25 | background(.clear) 26 | } 27 | declarativeView.layer.cornerRadius = 0 28 | let path = UIBezierPath(roundedRect: declarativeView.bounds, 29 | byRoundingCorners: UIRectCorner(customCorners.corners), 30 | cornerRadii: CGSize(width: customCorners.radius, height: customCorners.radius)) 31 | let maskLayer = CAShapeLayer() 32 | maskLayer.accessibilityLabel = "maskLayer.accessibilityLabel" 33 | maskLayer.path = path.cgPath 34 | maskLayer.fillColor = _declarativeView._properties.customCorners?.backgroundColor?.cgColor ?? UIColor.white.cgColor 35 | if declarativeView.layer.sublayers?.contains(where: { $0.accessibilityLabel == maskLayer.accessibilityLabel }) == true { 36 | declarativeView.layer.sublayers?.removeFirst() 37 | } 38 | declarativeView.layer.insertSublayer(maskLayer, at: 0) 39 | } 40 | } 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+MovedToSuperview.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension DeclarativeProtocol { 8 | func movedToSuperview() { 9 | for constraint in _declarativeView._properties.notAppliedPreConstraintsSolo { 10 | declarativeView.activateSolo(constraint) 11 | } 12 | for constraint in _declarativeView._properties.notAppliedPreConstraintsSuper { 13 | declarativeView.activateSuper(constraint) 14 | } 15 | for constraint in _declarativeView._properties.notAppliedPreConstraintsRelative { 16 | declarativeView.activateRelative(constraint) 17 | } 18 | NotificationCenter.default.post(raw: AddedViewWithTag(tag)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+NextResponder.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension DeclarativeProtocol { 8 | public func focusToNextResponderOrResign() { 9 | if let nextResponder = declarativeView.viewWithTagInSuperview(tag + 1) { 10 | nextResponder.becomeFirstResponder() 11 | } else { 12 | declarativeView.resignFirstResponder() 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Opacity.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension DeclarativeProtocol { 8 | public var opacity: State { properties.$opacity } 9 | 10 | @discardableResult 11 | public func opacity(_ opacity: Float) -> Self { 12 | #if os(macOS) 13 | declarativeView.layer?.opacity = opacity 14 | #else 15 | declarativeView.layer.opacity = opacity 16 | #endif 17 | return self 18 | } 19 | 20 | @discardableResult 21 | public func opacity(_ state: State) -> Self { 22 | #if os(macOS) 23 | declarativeView.layer?.opacity = state.wrappedValue 24 | #else 25 | declarativeView.layer.opacity = state.wrappedValue 26 | #endif 27 | properties.opacity = state.wrappedValue 28 | state.listen { [weak self] new in 29 | #if os(macOS) 30 | self?.declarativeView.layer?.opacity = new 31 | #else 32 | self?.declarativeView.layer.opacity = new 33 | #endif 34 | self?.properties.opacity = new 35 | } 36 | return self 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Rasterize.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | extension DeclarativeProtocol { 5 | @discardableResult 6 | public func rasterize(_ value: Bool = true) -> Self { 7 | declarativeView.layer.shouldRasterize = true 8 | declarativeView.layer.rasterizationScale = UIScreen.main.scale 9 | return self 10 | } 11 | } 12 | #endif 13 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Shadow.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension DeclarativeProtocol { 8 | @discardableResult 9 | public func shadow(_ colorNumber: Int, opacity: Float = 1, x: CGFloat = 0, y: CGFloat = 0, radius: CGFloat = 10) -> Self { 10 | shadow(colorNumber.color, opacity: opacity, x: x, y: y, radius: radius) 11 | } 12 | 13 | @discardableResult 14 | public func shadow(_ color: UColor = .black, opacity: Float = 1, x: CGFloat = 0, y: CGFloat = 0, radius: CGFloat = 10) -> Self { 15 | _setShadow(x: x, y: y) 16 | _setShadow(opacity: opacity) 17 | _setShadow(radius: radius) 18 | _setShadow(color: .init(wrappedValue: color)) 19 | return self 20 | } 21 | 22 | private func _setShadow(color: State) { 23 | #if os(macOS) 24 | properties.shadowColor.changeHandler = nil 25 | properties.shadowColor = color.wrappedValue 26 | declarativeView.layer?.shadowColor = color.wrappedValue.current.cgColor 27 | properties.shadowColor.onChange { [weak self] new in 28 | self?.declarativeView.layer?.shadowColor = new.cgColor 29 | } 30 | #else 31 | properties.shadowColor = color.wrappedValue 32 | declarativeView.layer.shadowColor = color.wrappedValue.cgColor 33 | #endif 34 | } 35 | 36 | private func _setShadow(opacity: Float) { 37 | #if os(macOS) 38 | declarativeView.layer?.shadowOpacity = opacity 39 | #else 40 | declarativeView.layer.shadowOpacity = opacity 41 | #endif 42 | } 43 | 44 | private func _setShadow(x: CGFloat, y: CGFloat) { 45 | #if os(macOS) 46 | declarativeView.wantsLayer = true 47 | declarativeView.shadow = NSShadow() 48 | declarativeView.layer?.shadowOffset = CGSize(width: x, height: y) 49 | #else 50 | declarativeView.layer.shadowOffset = CGSize(width: x, height: y) 51 | #endif 52 | } 53 | 54 | private func _setShadow(radius: CGFloat) { 55 | #if os(macOS) 56 | declarativeView.layer?.shadowRadius = radius 57 | #else 58 | declarativeView.layer.shadowRadius = radius 59 | #endif 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Tag.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension DeclarativeProtocol { 8 | @discardableResult 9 | public func tag(_ value: Int) -> Self { 10 | tag = value 11 | return self 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+Tint.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | extension DeclarativeProtocol { 5 | public var tint: State { properties.$tint } 6 | 7 | @discardableResult 8 | public func tint(_ color: UColor) -> Self { 9 | declarativeView.tintColor = color 10 | properties.tint = color 11 | #if targetEnvironment(macCatalyst) 12 | // FIXME: it crashes on UImage on `textInputTraits` since it doesn't have it 13 | if self is UImage == false, self is UButton == false { 14 | let textInputTraits = declarativeView.value(forKey: "textInputTraits") as? NSObject 15 | textInputTraits?.setValue(color, forKey: "insertionPointColor") 16 | } 17 | #endif 18 | return self 19 | } 20 | 21 | @discardableResult 22 | public func tint(_ number: Int) -> Self { 23 | tint(number.color) 24 | } 25 | 26 | @discardableResult 27 | public func tint(_ state: State) -> Self { 28 | tint(state.wrappedValue) 29 | state.listen { [weak self] new in 30 | self?.tint(new) 31 | } 32 | return self 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /Classes/Extensions/DeclarativeProtocol+UserInteraction.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | extension DeclarativeProtocol { 5 | public var userInteraction: State { properties.$userInteraction } 6 | 7 | @discardableResult 8 | public func userInteraction(_ enabled: Bool = true) -> Self { 9 | declarativeView.isUserInteractionEnabled = enabled 10 | properties.userInteraction = enabled 11 | return self 12 | } 13 | 14 | @discardableResult 15 | public func userInteraction(_ state: State) -> Self { 16 | declarativeView.isUserInteractionEnabled = state.wrappedValue 17 | properties.userInteraction = state.wrappedValue 18 | state.listen { [weak self] new in 19 | self?.declarativeView.isUserInteractionEnabled = new 20 | self?.properties.userInteraction = new 21 | } 22 | return self 23 | } 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /Classes/Extensions/NSLayoutConstraint+Activated.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension NSLayoutConstraint { 8 | @discardableResult 9 | func activated() -> NSLayoutConstraint { 10 | isActive = true 11 | return self 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Classes/Extensions/NSLayoutConstraint+Update.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension NSLayoutConstraint { 8 | @discardableResult 9 | func update(_ value: ConstraintValue) -> NSLayoutConstraint { 10 | constant = value.constraintValue.value 11 | #if !os(macOS) 12 | priority = value.constraintValue.priority 13 | #endif 14 | return self 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Classes/Extensions/NSSound+System.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | 4 | extension NSSound { 5 | public static let basso = NSSound(named: .basso) 6 | public static let blow = NSSound(named: .blow) 7 | public static let bottle = NSSound(named: .bottle) 8 | public static let frog = NSSound(named: .frog) 9 | public static let funk = NSSound(named: .funk) 10 | public static let glass = NSSound(named: .glass) 11 | public static let hero = NSSound(named: .hero) 12 | public static let morse = NSSound(named: .morse) 13 | public static let ping = NSSound(named: .ping) 14 | public static let pop = NSSound(named: .pop) 15 | public static let purr = NSSound(named: .purr) 16 | public static let sosumi = NSSound(named: .sosumi) 17 | public static let submarine = NSSound(named: .submarine) 18 | public static let tink = NSSound(named: .tink) 19 | } 20 | 21 | extension NSSound.Name { 22 | public static let basso = NSSound.Name("Basso") 23 | public static let blow = NSSound.Name("Blow") 24 | public static let bottle = NSSound.Name("Bottle") 25 | public static let frog = NSSound.Name("Frog") 26 | public static let funk = NSSound.Name("Funk") 27 | public static let glass = NSSound.Name("Glass") 28 | public static let hero = NSSound.Name("Hero") 29 | public static let morse = NSSound.Name("Morse") 30 | public static let ping = NSSound.Name("Ping") 31 | public static let pop = NSSound.Name("Pop") 32 | public static let purr = NSSound.Name("Purr") 33 | public static let sosumi = NSSound.Name("Sosumi") 34 | public static let submarine = NSSound.Name("Submarine") 35 | public static let tink = NSSound.Name("Tink") 36 | } 37 | #endif 38 | -------------------------------------------------------------------------------- /Classes/Extensions/NSView+BringToFront.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSView+BringToFront.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 26.08.2020. 6 | // 7 | 8 | #if os(macOS) 9 | import AppKit 10 | 11 | extension NSView { 12 | func bringSubviewToFront(_ view: NSView) { 13 | var theView = view 14 | self.sortSubviews({ viewA, viewB, rawPointer in 15 | let view = rawPointer?.load(as: NSView.self) 16 | switch view { 17 | case viewA: 18 | return .orderedDescending 19 | case viewB: 20 | return .orderedAscending 21 | default: 22 | return .orderedSame 23 | } 24 | }, context: &theView) 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Classes/Extensions/NavigationController+FadeTo.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | extension UINavigationController { 5 | public func pushViewControllerAnimated(_ viewController: UIViewController, animation: CATransitionType = .fade, duration: CFTimeInterval = 0.3) { 6 | let transition: CATransition = CATransition() 7 | transition.duration = duration 8 | transition.type = animation 9 | view.layer.add(transition, forKey: nil) 10 | pushViewController(viewController, animated: false) 11 | } 12 | 13 | public func popViewControllerAnimated(animation: CATransitionType = .fade, duration: CFTimeInterval = 0.3) { 14 | let transition: CATransition = CATransition() 15 | transition.duration = duration 16 | transition.type = animation 17 | view.layer.add(transition, forKey: nil) 18 | popViewController(animated: false) 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Classes/Extensions/Number+ConstraintValue.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(macOS) 3 | import AppKit 4 | #else 5 | import UIKit 6 | #endif 7 | 8 | extension Int: ConstraintValue { 9 | public var constraintValue: ConstraintValueType { 10 | .init(.equal, CGFloat(self)) 11 | } 12 | } 13 | 14 | extension Double: ConstraintValue { 15 | public var constraintValue: ConstraintValueType { 16 | .init(.equal, CGFloat(self)) 17 | } 18 | } 19 | 20 | extension CGFloat: ConstraintValue { 21 | public var constraintValue: ConstraintValueType { 22 | .init(.equal, self) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Classes/Extensions/String+LocalizedString.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | public init (_ ls: LocalizedString...) { 5 | self.init(ls) 6 | } 7 | 8 | public init (_ ls: [LocalizedString]) { 9 | guard ls.count > 0 else { 10 | self.init("") 11 | print("⚠️ Localization: ❌ EMPTY STRING") 12 | return 13 | } 14 | var lsByPrefix: LocalizedString? 15 | for ls in ls { 16 | switch ls.type { 17 | case .short: 18 | if Localization.current.rawValue.starts(with: ls.language.rawValue) { 19 | self.init(ls.value) 20 | return 21 | } 22 | case .long: 23 | if ls.language == Localization.current { 24 | self.init(ls.value) 25 | return 26 | } else if Localization.current.prefix.starts(with: ls.prefix) { 27 | lsByPrefix = ls 28 | } else if ls.prefix.starts(with: Localization.current.prefix) { 29 | lsByPrefix = ls 30 | } 31 | } 32 | } 33 | if let ls = lsByPrefix { 34 | self.init(ls.value) 35 | return 36 | } 37 | if let ls = ls.first(where: { 38 | $0.language.rawValue.contains(Localization.default.rawValue) 39 | || $0.prefix == Localization.default.prefix }) { 40 | self.init(ls.value) 41 | return 42 | } 43 | self.init("❔❔❔") 44 | print("⚠️ Localization: ❌ UNABLE TO DETECT LOCALE 🤬 set breakpoint here to find that string (current locale: \(Localization.current)") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Classes/Extensions/String+SegmentControlable.swift: -------------------------------------------------------------------------------- 1 | extension String: SegmentControlable { 2 | public var item: SegmentControlableItem { .title(self) } 3 | } 4 | -------------------------------------------------------------------------------- /Classes/Extensions/TableView+Cellable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(macOS) 3 | 4 | #else 5 | import UIKit 6 | 7 | extension UITableView { 8 | @discardableResult 9 | public func register(_ cellClass: Cellable.Type...) -> Self { 10 | cellClass.forEach { register($0, forCellReuseIdentifier: $0.reuseIdentifier) } 11 | return self 12 | } 13 | 14 | public func dequeueReusableCell(with class: T.Type, for indexPath: IndexPath) -> T { 15 | dequeueReusableCell(withIdentifier: `class`.reuseIdentifier, for: indexPath) as! T 16 | } 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /Classes/Extensions/UIColor+Aplha.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | extension UColor { 5 | public func alpha(_ value: CGFloat) -> UColor { 6 | var red: CGFloat = 0 7 | var green: CGFloat = 0 8 | var blue: CGFloat = 0 9 | getRed(&red, green: &green, blue: &blue, alpha: nil) 10 | return UColor(red: red, green: green, blue: blue, alpha: value) 11 | } 12 | } 13 | #endif 14 | -------------------------------------------------------------------------------- /Classes/Extensions/UIColor+Hex.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | public typealias UColor = Color 3 | #else 4 | public typealias UColor = UIColor 5 | extension UColor { 6 | public var current: UIColor { self } 7 | } 8 | #endif 9 | 10 | #if os(macOS) 11 | import AppKit 12 | #else 13 | import UIKit 14 | #endif 15 | 16 | extension UColor { 17 | public convenience init(red: Int, green: Int, blue: Int) { 18 | assert(red >= 0 && red <= 255, "Invalid red component") 19 | assert(green >= 0 && green <= 255, "Invalid green component") 20 | assert(blue >= 0 && blue <= 255, "Invalid blue component") 21 | #if os(macOS) 22 | self.init(NSColor.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)) 23 | #else 24 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0) 25 | #endif 26 | } 27 | 28 | public convenience init(netHex:Int) { 29 | self.init(red:(netHex >> 16) & 0xff, green:(netHex >> 8) & 0xff, blue:netHex & 0xff) 30 | } 31 | } 32 | 33 | extension Int { 34 | public var color: UColor { 35 | UColor(netHex: self) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Classes/Extensions/UIControl+ActionHandler.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | extension UIControl { 5 | private func actionHandler(action: (() -> Void)? = nil) { 6 | struct Storage { static var actions: [Int: (() -> Void)] = [:] } 7 | if let action = action { 8 | Storage.actions[hashValue] = action 9 | } else { 10 | Storage.actions[hashValue]?() 11 | } 12 | } 13 | 14 | @objc func triggerActionHandler() { 15 | actionHandler() 16 | } 17 | 18 | func actionHandler(controlEvents control: UIControl.Event, forAction action: @escaping () -> Void) { 19 | actionHandler(action: action) 20 | addTarget(self, action: #selector(triggerActionHandler), for: control) 21 | } 22 | } 23 | #endif 24 | -------------------------------------------------------------------------------- /Classes/Extensions/UIDeviceOrientation+Description.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIDeviceOrientation+Description.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 09.09.2020. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | extension UIDeviceOrientation: CustomStringConvertible { 12 | public var description: String { 13 | switch self { 14 | case .landscapeLeft: return "landscapeLeft" 15 | case .landscapeRight: return "landscapeRight" 16 | case .portrait: return "portrait" 17 | case .portraitUpsideDown: return "portraitUpsideDown" 18 | case .faceDown: return "faceDown" 19 | case .faceUp: return "faceUp" 20 | case .unknown: return "unknown" 21 | } 22 | } 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /Classes/Extensions/UIFont+Family.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | public typealias UFont = NSFont 3 | #else 4 | public typealias UFont = UIFont 5 | #endif 6 | 7 | #if os(macOS) 8 | import AppKit 9 | #else 10 | import UIKit 11 | #endif 12 | 13 | extension UFont { 14 | public convenience init? (_ identifier: FontIdentifier, size: CGFloat) { 15 | self.init(name: identifier.fontName, size: size) 16 | } 17 | } 18 | 19 | public protocol FontIdentifierable { 20 | var fontName: String { get } 21 | } 22 | 23 | public struct FontIdentifier: FontIdentifierable { 24 | public let fontName: String 25 | 26 | public init (_ fontName: String) { 27 | self.fontName = fontName 28 | } 29 | } 30 | 31 | /** Usage example 32 | extension FontIdentifier { 33 | public static var sfProBold = FontIdentifier("SFProDisplay-Bold") 34 | public static var sfProRegular = FontIdentifier("SFProDisplay-Regular") 35 | public static var sfProMedium = FontIdentifier("SFProDisplay-Medium") 36 | } 37 | 38 | And then somewhere 39 | 40 | Label("hello world").font(.sfProRegular, 16) 41 | TextField().font(.sfProRegular, 16) 42 | */ 43 | -------------------------------------------------------------------------------- /Classes/Extensions/UIFont+PrintAll.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | #if os(macOS) 8 | 9 | #else 10 | extension UIFont { 11 | public static func printAll() { 12 | let familyNames = UIFont.familyNames 13 | for family in familyNames { 14 | debugPrint("Family name " + family) 15 | let fontNames = UIFont.fontNames(forFamilyName: family) 16 | for font in fontNames { 17 | debugPrint(" Font name: " + font) 18 | } 19 | } 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Classes/Extensions/UIGestureRecognizer+GestureRecognizerable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | 4 | public typealias UGestureRecognizer = NSGestureRecognizer 5 | public typealias UGestureRecognizerDelegate = NSGestureRecognizerDelegate 6 | #else 7 | import UIKit 8 | 9 | public typealias UGestureRecognizer = UIGestureRecognizer 10 | public typealias UGestureRecognizerDelegate = UIGestureRecognizerDelegate 11 | #endif 12 | 13 | extension UGestureRecognizer: _GestureRecognizerable { 14 | func _setDelegate(_ v: UGestureRecognizerDelegate) { 15 | delegate = v 16 | } 17 | 18 | func _setEnabled(_ v: Bool) { 19 | isEnabled = v 20 | } 21 | 22 | #if !os(macOS) 23 | func _setCancelsTouchesInView(_ v: Bool) { 24 | cancelsTouchesInView = v 25 | } 26 | 27 | func _setDelaysTouchesBegan(_ v: Bool) { 28 | delaysTouchesBegan = v 29 | } 30 | 31 | func _setDelaysTouchesEnded(_ v: Bool) { 32 | delaysTouchesEnded = v 33 | } 34 | 35 | func _setName(_ v: String) { 36 | if #available(iOS 11.0, *) { 37 | name = v 38 | } 39 | } 40 | 41 | func _setRequiresExclusiveTouchType(_ v: Bool) { 42 | if #available(iOS 9.2, *) { 43 | requiresExclusiveTouchType = v 44 | } 45 | } 46 | 47 | func _setAllowedTouchTypes(_ v: [NSNumber]) { 48 | allowedTouchTypes = v 49 | } 50 | 51 | func _setAllowedPressTypes(_ v: [NSNumber]) { 52 | allowedPressTypes = v 53 | } 54 | #endif 55 | 56 | func _setRequireToFailOtherGestureRecognizer(_ v: UGestureRecognizer) { 57 | require(toFail: v) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Classes/Extensions/UIImage+Blur.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(macOS) 3 | import AppKit 4 | 5 | public typealias _UImageView = NSImageView 6 | public typealias _UImage = NSImage 7 | #else 8 | import UIKit 9 | 10 | public typealias _UImageView = UIImageView 11 | public typealias _UImage = UIImage 12 | #endif 13 | 14 | #if !os(macOS) 15 | extension _UImage { 16 | public func blurred(_ radius: CGFloat = 10) -> _UImage? { 17 | let inputImage = CIImage(cgImage: (cgImage)!) 18 | let filter = CIFilter(name: "CIGaussianBlur") 19 | filter?.setValue(inputImage, forKey: "inputImage") 20 | filter?.setValue(radius, forKey: "inputRadius") 21 | let blurred = filter?.outputImage 22 | 23 | var newImageSize: CGRect = (blurred?.extent)! 24 | newImageSize.origin.x += (newImageSize.size.width - size.width) / 2 25 | newImageSize.origin.y += (newImageSize.size.height - size.height) / 2 26 | newImageSize.size = size 27 | 28 | let resultImage = filter?.value(forKey: "outputImage") as! CIImage 29 | let context = CIContext(options: nil) 30 | let cgimg = context.createCGImage(resultImage, from: newImageSize)! 31 | let blurredImage = _UImage(cgImage: cgimg) 32 | return blurredImage 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /Classes/Extensions/UIImage+Resize.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(macOS) 3 | import AppKit 4 | #else 5 | import UIKit 6 | 7 | extension UIImage { 8 | public func resizeWith(percentage: CGFloat) -> UIImage? { 9 | let imageView = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: size.width * percentage, height: size.height * percentage))) 10 | imageView.contentMode = .scaleAspectFit 11 | imageView.image = self 12 | UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale) 13 | guard let context = UIGraphicsGetCurrentContext() else { return nil } 14 | imageView.layer.render(in: context) 15 | guard let result = UIGraphicsGetImageFromCurrentImageContext() else { return nil } 16 | UIGraphicsEndImageContext() 17 | return result 18 | } 19 | 20 | public func resizeWith(width: CGFloat) -> UIImage? { 21 | let imageView = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height))))) 22 | imageView.contentMode = .scaleAspectFit 23 | imageView.image = self 24 | UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale) 25 | guard let context = UIGraphicsGetCurrentContext() else { return nil } 26 | imageView.layer.render(in: context) 27 | guard let result = UIGraphicsGetImageFromCurrentImageContext() else { return nil } 28 | UIGraphicsEndImageContext() 29 | return result 30 | } 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /Classes/Extensions/UIImage+SegmentControlable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension _UImage: SegmentControlable { 8 | public var item: SegmentControlableItem { .image(self) } 9 | } 10 | -------------------------------------------------------------------------------- /Classes/Extensions/UIInterfaceOrientation+Description.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIInterfaceOrientation+Description.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 09.09.2020. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | extension UIInterfaceOrientation: CustomStringConvertible { 12 | public var description: String { 13 | switch self { 14 | case .landscapeLeft: return "landscapeLeft" 15 | case .landscapeRight: return "landscapeRight" 16 | case .portrait: return "portrait" 17 | case .portraitUpsideDown: return "portraitUpsideDown" 18 | case .unknown: return "unknown" 19 | } 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Classes/Extensions/UIViewController+Body.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension BaseViewController { 8 | @discardableResult 9 | open func body(@BodyBuilder block: BodyBuilder.SingleView) -> Self { 10 | view.body { block() } 11 | return self 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Classes/Extensions/UIVisualEffect+Effects.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | extension UIVisualEffect { 5 | public static var darkBlur: UIVisualEffect { UIBlurEffect(style: .dark) } 6 | public static var lightBlur: UIVisualEffect { UIBlurEffect(style: .light) } 7 | public static var extraLightBlur: UIVisualEffect { UIBlurEffect(style: .extraLight) } 8 | @available(iOS 10.0, *) 9 | public static var prominentBlur: UIVisualEffect {UIBlurEffect(style: .prominent) } 10 | @available(iOS 10.0, *) 11 | public static var regularBlur: UIVisualEffect { UIBlurEffect(style: .regular) } 12 | } 13 | #endif 14 | -------------------------------------------------------------------------------- /Classes/Extensions/UNAuthorizationStatus+Status.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UNAuthorizationStatus+Status.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 10.09.2020. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | @available(iOS 10.0, *) 12 | extension UNAuthorizationStatus { 13 | var status: PushNotificationsAuthorizationStatus { 14 | switch self { 15 | case .authorized: return .authorized 16 | case .denied: return .denied 17 | case .notDetermined: return .notDetermined 18 | #if swift(>=5.3) 19 | case .ephemeral: return .notDetermined 20 | #endif 21 | case .provisional: 22 | if #available(iOS 12.0, *) { 23 | return .provisional 24 | } else { 25 | return .authorized 26 | } 27 | } 28 | } 29 | } 30 | #endif 31 | -------------------------------------------------------------------------------- /Classes/Extensions/View+Add.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension BaseView { 8 | public func addSubview(_ views: [BaseView]) { 9 | views.forEach { self.addSubview($0) } 10 | } 11 | 12 | public func addSubview(_ views: BaseView...) { 13 | addSubview(views) 14 | } 15 | 16 | public func addSubview(_ view: ()->(V)) where V: DeclarativeProtocol { 17 | body { view().declarativeView } 18 | } 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /Classes/Extensions/View+Body.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension BaseView { 8 | @discardableResult 9 | open func body(@BodyBuilder block: BodyBuilder.SingleView) -> Self { 10 | addItem(block()) 11 | return self 12 | } 13 | 14 | func addItem(_ item: BodyBuilderItemable, at index: Int? = nil) { 15 | addItem(item.bodyBuilderItem, at: index) 16 | } 17 | 18 | func addItem(_ item: BodyBuilderItem, at index: Int? = nil) { 19 | switch item { 20 | case .single(let view): 21 | add(views: [view], at: index) 22 | case .multiple(let views): 23 | add(views: views, at: index) 24 | case .forEach(let fr): 25 | let subview = UView().edgesToSuperview() 26 | fr.allItems().forEach { 27 | subview.addItem($0) 28 | } 29 | add(views: [subview], at: index) 30 | fr.subscribeToChanges({}, { deletions, insertions, _ in 31 | subview.subviews.removeFromSuperview(at: deletions) 32 | insertions.forEach { 33 | subview.addItem(fr.items(at: $0), at: $0) 34 | } 35 | }) {} 36 | break 37 | case .nested(let items): 38 | items.forEach { addItem($0, at: index) } 39 | break 40 | case .none: 41 | break 42 | } 43 | } 44 | 45 | func add(views: [BaseView], at index: Int?) { 46 | guard let index = index else { 47 | addSubview(views) 48 | return 49 | } 50 | let nextViews = subviews.dropFirst(index) 51 | nextViews.forEach { $0.removeFromSuperview() } 52 | addSubview(views) 53 | nextViews.forEach { self.addSubview($0) } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Classes/Extensions/View+Menuable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View+Menuable.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 22.08.2020. 6 | // 7 | 8 | #if os(macOS) 9 | import AppKit 10 | 11 | extension BaseView: _Menuable { 12 | func _setMenu(_ v: Menu) { 13 | menu = v.menu 14 | } 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /Classes/Extensions/View+ViewWithTag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+ViewWithTag.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 18.04.2020. 6 | // 7 | 8 | #if os(macOS) 9 | import AppKit 10 | #else 11 | import UIKit 12 | #endif 13 | 14 | extension BaseView { 15 | public func viewWithTagInSuperview(_ tag: Int) -> BaseView? { 16 | if let view = viewWithTag(tag) { 17 | return view 18 | } else if let view = superview?.viewWithTag(tag) { 19 | return view 20 | } else if let view = superview?.superview?.viewWithTag(tag) { 21 | return view 22 | } else if let view = superview?.superview?.superview?.viewWithTag(tag) { 23 | return view 24 | } else if let view = superview?.superview?.superview?.superview?.viewWithTag(tag) { 25 | return view 26 | } else if let view = superview?.superview?.superview?.superview?.superview?.viewWithTag(tag) { 27 | return view 28 | } else if let view = superview?.superview?.superview?.superview?.superview?.superview?.viewWithTag(tag) { 29 | return view 30 | } else if let view = superview?.superview?.superview?.superview?.superview?.superview?.superview?.viewWithTag(tag) { 31 | return view 32 | } else if let view = superview?.superview?.superview?.superview?.superview?.superview?.superview?.superview?.viewWithTag(tag) { 33 | return view 34 | } else if let view = superview?.superview?.superview?.superview?.superview?.superview?.superview?.superview?.superview?.viewWithTag(tag) { 35 | return view 36 | } else if let view = superview?.superview?.superview?.superview?.superview?.superview?.superview?.superview?.superview?.superview?.viewWithTag(tag) { 37 | return view 38 | } 39 | return nil 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Classes/Extensions/WrappedViewControllerable+Hidden.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | extension WrappedViewControllerable { 8 | public func hidden(_ hidden: Bool = true) { 9 | guard hidden == !protocolView.isHidden else { return } 10 | protocolView.hidden(hidden) 11 | #if os(macOS) 12 | if hidden { 13 | protocolController?.viewWillDisappear() 14 | protocolController?.viewDidDisappear() 15 | } else { 16 | protocolController?.viewWillAppear() 17 | protocolController?.viewDidAppear() 18 | } 19 | #else 20 | if hidden { 21 | protocolController?.viewWillDisappear(false) 22 | protocolController?.viewDidDisappear(false) 23 | } else { 24 | protocolController?.viewWillAppear(false) 25 | protocolController?.viewDidAppear(false) 26 | } 27 | #endif 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Classes/Objects/ClickGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | 4 | final public class ClickGestureRecognizer: NSClickGestureRecognizer, _GestureTrackable, _GestureDelegatorable { 5 | var _tracker = _GestureTracker() 6 | var _delegator = _GestureDelegator() 7 | 8 | public init(clicks: Int = 1, touches: Int = 1, buttonMask: Int = 0x1) { 9 | super.init(target: _tracker, action: #selector(_tracker.handle)) 10 | self.numberOfClicksRequired = clicks 11 | self.numberOfTouchesRequired = touches 12 | self.buttonMask = buttonMask 13 | delegate = _delegator 14 | } 15 | 16 | required init?(coder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | @discardableResult 21 | public func numberOfClicksRequired(_ v: Int) -> Self { 22 | numberOfClicksRequired = v 23 | return self 24 | } 25 | 26 | @discardableResult 27 | public func numberOfClicksRequired(_ state: UIKitPlus.State) -> Self { 28 | state.listen { [weak self] in 29 | self?.numberOfClicksRequired = $0 30 | } 31 | return self 32 | } 33 | 34 | @discardableResult 35 | public func numberOfTouchesRequired(_ v: Int) -> Self { 36 | numberOfTouchesRequired = v 37 | return self 38 | } 39 | 40 | @discardableResult 41 | public func numberOfTouchesRequired(_ state: UIKitPlus.State) -> Self { 42 | state.listen { [weak self] in 43 | self?.numberOfTouchesRequired = $0 44 | } 45 | return self 46 | } 47 | 48 | @discardableResult 49 | public func buttonMask(_ v: Int) -> Self { 50 | buttonMask = v 51 | return self 52 | } 53 | 54 | @discardableResult 55 | public func buttonMask(_ state: UIKitPlus.State) -> Self { 56 | state.listen { [weak self] in 57 | self?.buttonMask = $0 58 | } 59 | return self 60 | } 61 | 62 | var _tag: Int = 0 63 | public override var tag: Int { 64 | get { _tag } 65 | set { _tag = newValue } 66 | } 67 | } 68 | #endif 69 | -------------------------------------------------------------------------------- /Classes/Objects/GestureTracker.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | class _GestureTracker { 8 | var change: ((UGestureRecognizer.State) -> Void)? 9 | var possible: (() -> Void)? 10 | var began: (() -> Void)? 11 | var changed: (() -> Void)? 12 | var ended: (() -> Void)? 13 | var cancelled: (() -> Void)? 14 | var failed: (() -> Void)? 15 | 16 | weak var outerDelegate: UGestureRecognizerDelegate? 17 | 18 | init () {} 19 | 20 | #if os(macOS) 21 | @objc func handle(_ gesture: NSGestureRecognizer) { 22 | change?(gesture.state) 23 | switch gesture.state { 24 | case .possible: possible?() 25 | case .began: began?() 26 | case .changed: changed?() 27 | case .ended: ended?() 28 | case .cancelled: cancelled?() 29 | case .failed: failed?() 30 | @unknown default: 31 | assertionFailure("GestureTracker case not supported") 32 | } 33 | } 34 | #else 35 | @objc func handle(_ gesture: UILongPressGestureRecognizer) { 36 | change?(gesture.state) 37 | switch gesture.state { 38 | case .possible: possible?() 39 | case .began: began?() 40 | case .changed: changed?() 41 | case .ended: ended?() 42 | case .cancelled: cancelled?() 43 | case .failed: failed?() 44 | @unknown default: 45 | assertionFailure("GestureTracker case not supported") 46 | } 47 | } 48 | #endif 49 | } 50 | -------------------------------------------------------------------------------- /Classes/Objects/HoverGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | #if !os(tvOS) 5 | @available(iOS 13.0, *) 6 | final public class HoverGestureRecognizer: UIHoverGestureRecognizer, _GestureTrackable, _GestureDelegatorable { 7 | var _tracker = _GestureTracker() 8 | var _delegator = _GestureDelegator() 9 | 10 | public init() { 11 | super.init(target: _tracker, action: #selector(_tracker.handle)) 12 | delegate = _delegator 13 | } 14 | 15 | var _tag: Int = 0 16 | public override var tag: Int { 17 | get { _tag } 18 | set { _tag = newValue } 19 | } 20 | } 21 | #endif 22 | #endif 23 | -------------------------------------------------------------------------------- /Classes/Objects/ImpactFeedback.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public class ImpactFeedback { 5 | public enum FeedbackStyle: Int { 6 | case light, medium, heavy 7 | } 8 | #if !os(tvOS) 9 | public static func bzz(_ style: FeedbackStyle = .light) { 10 | if #available(iOS 10.0, *) { 11 | guard let style = UIImpactFeedbackGenerator.FeedbackStyle(rawValue: style.rawValue) else { return } 12 | UIImpactFeedbackGenerator(style: style).impactOccurred() 13 | } 14 | } 15 | 16 | public static func selected() { 17 | if #available(iOS 10.0, *) { 18 | UISelectionFeedbackGenerator().selectionChanged() 19 | } 20 | } 21 | 22 | public static func success() { 23 | if #available(iOS 10.0, *) { 24 | UINotificationFeedbackGenerator().notificationOccurred(.success) 25 | } 26 | } 27 | 28 | public static func warning() { 29 | if #available(iOS 10.0, *) { 30 | UINotificationFeedbackGenerator().notificationOccurred(.warning) 31 | } 32 | } 33 | 34 | public static func error() { 35 | if #available(iOS 10.0, *) { 36 | UINotificationFeedbackGenerator().notificationOccurred(.error) 37 | } 38 | } 39 | #endif 40 | } 41 | #endif 42 | -------------------------------------------------------------------------------- /Classes/Objects/Localization.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | private var localization = Localization() 4 | 5 | public class Localization { 6 | let currentLocaleIdentifier = Locale.preferredLanguages.first ?? Locale.current.identifier 7 | 8 | var defaultLanguage: Language = .en 9 | 10 | lazy var currentLanguage: Language = detectCurrentLanguage() 11 | 12 | func detectCurrentLanguage() -> Language { 13 | if let language = Language(rawValue: currentLocaleIdentifier) { 14 | return language 15 | } 16 | if let locale = currentLocaleIdentifier.components(separatedBy: "_").first { 17 | if let language = Language(rawValue: locale) { 18 | return language 19 | } else if let locale = locale.components(separatedBy: "-").first, let language = Language(rawValue: locale) { 20 | return language 21 | } 22 | } 23 | 24 | return defaultLanguage 25 | } 26 | 27 | static var shared: Localization { 28 | return localization 29 | } 30 | 31 | public static var current: Language { 32 | get { shared.currentLanguage } 33 | set { shared.currentLanguage = newValue } 34 | } 35 | 36 | public static var `default`: Language { 37 | get { shared.defaultLanguage } 38 | set { shared.defaultLanguage = newValue } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Classes/Objects/MagnificationGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | 4 | /// This object tracks pinch gestures on a track pad or other input device and stores the resulting magnification value for you to use in your code. 5 | /// This gesture recognizer automatically sets the value of the delaysMagnificationEvents property to true. 6 | final public class MagnificationGestureRecognizer: NSMagnificationGestureRecognizer, _GestureTrackable, _GestureDelegatorable { 7 | var _tracker = _GestureTracker() 8 | var _delegator = _GestureDelegator() 9 | 10 | public init(magnification: CGFloat? = nil) { 11 | super.init(target: _tracker, action: #selector(_tracker.handle)) 12 | if let magnification = magnification { 13 | self.magnification = magnification 14 | } 15 | delegate = _delegator 16 | } 17 | 18 | required init?(coder: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | @discardableResult 23 | public func magnification(_ v: CGFloat) -> Self { 24 | magnification = v 25 | return self 26 | } 27 | 28 | @discardableResult 29 | public func magnification(_ state: UIKitPlus.State) -> Self { 30 | state.listen { [weak self] in 31 | self?.magnification = $0 32 | } 33 | return self 34 | } 35 | 36 | var _tag: Int = 0 37 | public override var tag: Int { 38 | get { _tag } 39 | set { _tag = newValue } 40 | } 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /Classes/Objects/PinchGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | #if !os(tvOS) 5 | final public class PinchGestureRecognizer: UIPinchGestureRecognizer, _GestureTrackable, _GestureDelegatorable { 6 | var _tracker = _GestureTracker() 7 | var _delegator = _GestureDelegator() 8 | 9 | public init(scale: CGFloat? = nil) { 10 | super.init(target: _tracker, action: #selector(_tracker.handle)) 11 | if let scale = scale { 12 | self.scale = scale 13 | } 14 | delegate = _delegator 15 | } 16 | 17 | @discardableResult 18 | public func scale(_ v: CGFloat) -> Self { 19 | scale = v 20 | return self 21 | } 22 | 23 | @discardableResult 24 | public func scale(_ state: UIKitPlus.State) -> Self { 25 | state.listen { [weak self] in 26 | self?.scale = $0 27 | } 28 | return self 29 | } 30 | 31 | var _tag: Int = 0 32 | public override var tag: Int { 33 | get { _tag } 34 | set { _tag = newValue } 35 | } 36 | } 37 | #endif 38 | #endif 39 | -------------------------------------------------------------------------------- /Classes/Objects/PropertiesInternal.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public class PropertiesInternal { 8 | var circleCorners: Bool = false 9 | #if !os(macOS) 10 | var customCorners: CustomCorners? 11 | #endif 12 | lazy var borders = Borders() 13 | 14 | /// See `Textable` 15 | 16 | #if !os(macOS) 17 | var textChangeTransition: UIView.AnimationOptions? 18 | #endif 19 | var statedText: AnyStringBuilder.Handler? 20 | var textChangeListeners: [(NSAttributedString) -> Void] = [] 21 | 22 | /// See `Placeholderable` 23 | 24 | #if !os(macOS) 25 | var placeholderChangeTransition: UIView.AnimationOptions? 26 | #endif 27 | var statedPlaceholder: AnyStringBuilder.Handler? 28 | var placeholderBinding: State? 29 | @State var placeholderAttrText: NSAttributedString? 30 | 31 | /// See `Typeable` 32 | 33 | @State var isTyping = false 34 | var isTypingState: State { _isTyping } 35 | 36 | var typingInterval: TimeInterval = 0.5 37 | var typingTimer: Timer? 38 | 39 | // MARK: - Internal Constraints 40 | 41 | var notAppliedPreConstraintsSuper: [PreConstraint] = [] 42 | var appliedPreConstraintsSuper: [PreConstraint] = [] 43 | 44 | var notAppliedPreConstraintsSolo: [PreConstraint] = [] 45 | var appliedPreConstraintsSolo: [PreConstraint] = [] 46 | 47 | var notAppliedPreConstraintsRelative: [PreConstraint] = [] 48 | var appliedPreConstraintsRelative: [PreConstraint] = [] 49 | 50 | func moveAppliedToNotApplied() { 51 | appliedPreConstraintsSuper.forEach { 52 | $0.value.removeAllListeners() 53 | notAppliedPreConstraintsSuper.append($0) 54 | } 55 | appliedPreConstraintsSuper.removeAll() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Classes/Objects/ScreenEdgePanGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | #if !os(tvOS) 5 | final public class ScreenEdgePanGestureRecognizer: UIScreenEdgePanGestureRecognizer, _GestureTrackable, _GestureDelegatorable { 6 | var _tracker = _GestureTracker() 7 | var _delegator = _GestureDelegator() 8 | 9 | public init(edges: UIRectEdge) { 10 | super.init(target: _tracker, action: #selector(_tracker.handle)) 11 | self.edges = edges 12 | delegate = _delegator 13 | } 14 | 15 | @discardableResult 16 | public func edges(_ v: UIRectEdge) -> Self { 17 | edges = v 18 | return self 19 | } 20 | 21 | @discardableResult 22 | public func edges(_ state: UIKitPlus.State) -> Self { 23 | state.listen { [weak self] in 24 | self?.edges = $0 25 | } 26 | return self 27 | } 28 | 29 | var _tag: Int = 0 30 | public override var tag: Int { 31 | get { _tag } 32 | set { _tag = newValue } 33 | } 34 | } 35 | #endif 36 | #endif 37 | -------------------------------------------------------------------------------- /Classes/Objects/TapGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | final public class TapGestureRecognizer: UITapGestureRecognizer, _GestureTrackable, _GestureDelegatorable { 5 | var _tracker = _GestureTracker() 6 | var _delegator = _GestureDelegator() 7 | 8 | public init(taps: Int = 1, touches: Int = 1) { 9 | super.init(target: _tracker, action: #selector(_tracker.handle)) 10 | numberOfTapsRequired = taps 11 | #if !os(tvOS) 12 | numberOfTouchesRequired = touches 13 | #endif 14 | delegate = _delegator 15 | } 16 | 17 | @discardableResult 18 | public func numberOfTapsRequired(_ v: Int) -> Self { 19 | numberOfTapsRequired = v 20 | return self 21 | } 22 | 23 | @discardableResult 24 | public func numberOfTapsRequired(_ state: UIKitPlus.State) -> Self { 25 | state.listen { [weak self] in 26 | self?.numberOfTapsRequired = $0 27 | } 28 | return self 29 | } 30 | #if !os(tvOS) 31 | @discardableResult 32 | public func numberOfTouchesRequired(_ v: Int) -> Self { 33 | numberOfTouchesRequired = v 34 | return self 35 | } 36 | 37 | @discardableResult 38 | public func numberOfTouchesRequired(_ state: UIKitPlus.State) -> Self { 39 | state.listen { [weak self] in 40 | self?.numberOfTouchesRequired = $0 41 | } 42 | return self 43 | } 44 | #endif 45 | var _tag: Int = 0 46 | public override var tag: Int { 47 | get { _tag } 48 | set { _tag = newValue } 49 | } 50 | } 51 | #endif 52 | -------------------------------------------------------------------------------- /Classes/Objects/UIViewPropertyAnimator.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// UIViewPropertyAnimator.swift 3 | //// UIKit-Plus 4 | //// 5 | //// Created by Mihael Isaev on 03.03.2020. 6 | //// 7 | // 8 | //import UIKit 9 | // 10 | //@available(iOS 10.0, *) 11 | //extension UIViewPropertyAnimator { 12 | // public func delay(_ value: TimeInterval) -> Self { 13 | // delay = value 14 | // return self 15 | // } 16 | // 17 | // 18 | // /// Defaults to 0. This property is set when calling -[UIView startAnimationAfterDelay:]. 19 | // open var delay: TimeInterval { get } 20 | // 21 | // 22 | // /// Defaults to YES. Raises if set on an active animator. 23 | // open var isUserInteractionEnabled: Bool 24 | // 25 | // 26 | // /// Defaults to NO. Set if you need to manage the the hittesting of animating view hierarchies 27 | // open var isManualHitTestingEnabled: Bool 28 | // 29 | // 30 | // /// Defaults to YES. Raises if set on an active animator. 31 | // open var isInterruptible: Bool 32 | // 33 | // 34 | // /// Defaults to YES. Provides the ability for an animator to pause and scrub either linearly or using the animator’s current timing. 35 | // @available(iOS 11.0, *) 36 | // open var scrubsLinearly: Bool 37 | // 38 | // 39 | // /// Defaults to NO. Provides the ability for an animator to pause on completion instead of transitioning to the .inactive state. 40 | // @available(iOS 11.0, *) 41 | // open var pausesOnCompletion: Bool 42 | //} 43 | -------------------------------------------------------------------------------- /Classes/Protocols/Alternateable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Alternateable: AnyObject { 8 | @discardableResult 9 | func alternate() -> Self 10 | 11 | @discardableResult 12 | func alternate(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func alternate(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _Alternateable: Alternateable { 19 | func _setAlternate(_ v: Bool) 20 | } 21 | 22 | extension Alternateable { 23 | @discardableResult 24 | public func alternate() -> Self { 25 | alternate(true) 26 | } 27 | 28 | @discardableResult 29 | public func alternate(_ binding: UIKitPlus.State) -> Self { 30 | binding.listen { [weak self] in 31 | self?.alternate($0) 32 | } 33 | return alternate(binding.wrappedValue) 34 | } 35 | } 36 | 37 | @available(iOS 13.0, *) 38 | extension Alternateable { 39 | @discardableResult 40 | public func alternate(_ value: Bool) -> Self { 41 | guard let s = self as? _Alternateable else { return self } 42 | s._setAlternate(value) 43 | return self 44 | } 45 | } 46 | 47 | // for iOS lower than 13 48 | extension _Alternateable { 49 | @discardableResult 50 | public func alternate(_ value: Bool) -> Self { 51 | _setAlternate(value) 52 | return self 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Classes/Protocols/ArrowPositionable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | 4 | public protocol ArrowPositionable: AnyObject { 5 | @discardableResult 6 | func arrowPosition(_ value: NSPopUpButton.ArrowPosition) -> Self 7 | 8 | @discardableResult 9 | func arrowPosition(_ binding: UIKitPlus.State) -> Self 10 | } 11 | 12 | protocol _ArrowPositionable: ArrowPositionable { 13 | func _setArrowPosition(_ v: NSPopUpButton.ArrowPosition) 14 | } 15 | 16 | extension ArrowPositionable { 17 | @discardableResult 18 | public func arrowPosition(_ binding: UIKitPlus.State) -> Self { 19 | binding.listen { [weak self] in 20 | self?.arrowPosition($0) 21 | } 22 | return arrowPosition(binding.wrappedValue) 23 | } 24 | } 25 | 26 | @available(iOS 13.0, macOS 10.15, *) 27 | extension ArrowPositionable { 28 | @discardableResult 29 | public func arrowPosition(_ value: NSPopUpButton.ArrowPosition) -> Self { 30 | guard let s = self as? _ArrowPositionable else { return self } 31 | s._setArrowPosition(value) 32 | return self 33 | } 34 | } 35 | 36 | // for iOS lower than 13 37 | extension _ArrowPositionable { 38 | @discardableResult 39 | public func arrowPosition(_ value: NSPopUpButton.ArrowPosition) -> Self { 40 | _setArrowPosition(value) 41 | return self 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Classes/Protocols/BackgroundColorable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol BackgroundColorable: AnyObject { 8 | @discardableResult 9 | func background(_ color: UColor) -> Self 10 | 11 | @discardableResult 12 | func background(_ number: Int) -> Self 13 | 14 | @discardableResult 15 | func background(_ color: State) -> Self 16 | } 17 | 18 | protocol _BackgroundColorable: BackgroundColorable { 19 | var _backgroundColorState: State { get } 20 | 21 | #if os(macOS) 22 | func _setBackgroundColor(_ v: NSColor?) 23 | #else 24 | func _setBackgroundColor(_ v: UColor?) 25 | #endif 26 | } 27 | 28 | extension BackgroundColorable { 29 | @discardableResult 30 | public func background(_ number: Int) -> Self { 31 | background(number.color) 32 | } 33 | 34 | @discardableResult 35 | public func background(_ state: State) -> Self { 36 | state.listen { [weak self] in 37 | self?.background($0) 38 | } 39 | return background(state.wrappedValue) 40 | } 41 | } 42 | 43 | @available(iOS 13.0, *) 44 | extension BackgroundColorable { 45 | @discardableResult 46 | public func background(_ color: UColor) -> Self { 47 | guard let s = self as? _BackgroundColorable else { return self } 48 | _background(color, on: s) 49 | return self 50 | } 51 | } 52 | 53 | // for iOS lower than 13 54 | extension _BackgroundColorable { 55 | @discardableResult 56 | public func background(_ color: UColor) -> Self { 57 | _background(color, on: self) 58 | return self 59 | } 60 | } 61 | 62 | private func _background(_ color: UColor, on s: _BackgroundColorable) { 63 | #if os(macOS) 64 | s._backgroundColorState.wrappedValue.changeHandler = nil 65 | #endif 66 | s._backgroundColorState.wrappedValue = color 67 | s._setBackgroundColor(color.current) 68 | #if os(macOS) 69 | s._backgroundColorState.wrappedValue.onChange { [weak s] new in 70 | s?._setBackgroundColor(new) 71 | } 72 | #endif 73 | } 74 | -------------------------------------------------------------------------------- /Classes/Protocols/BezelStyleable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import Cocoa 3 | 4 | public protocol BezelStyleable: AnyObject { 5 | @discardableResult 6 | func style(_ value: NSButton.BezelStyle) -> Self 7 | 8 | @discardableResult 9 | func style(_ binding: UIKitPlus.State) -> Self 10 | } 11 | 12 | protocol _BezelStyleable: BezelStyleable { 13 | var _bezelStyleState: State { get set } 14 | 15 | func _setBezelStyle(_ v: NSButton.BezelStyle) 16 | } 17 | 18 | @available(iOS 13.0, *) 19 | extension BezelStyleable { 20 | @discardableResult 21 | public func style(_ binding: UIKitPlus.State) -> Self { 22 | guard var s = self as? _BezelStyleable else { return self } 23 | s._bezelStyleState = binding 24 | s._setBezelStyle(binding.wrappedValue) 25 | binding.listen { s._setBezelStyle($0) } 26 | return self 27 | } 28 | 29 | @discardableResult 30 | public func style(_ value: NSButton.BezelStyle) -> Self { 31 | guard let s = self as? _BezelStyleable else { return self } 32 | s._bezelStyleState.wrappedValue = value 33 | s._setBezelStyle(value) 34 | return self 35 | } 36 | } 37 | 38 | // for iOS lower than 13 39 | extension _BezelStyleable { 40 | @discardableResult 41 | public func style(_ binding: UIKitPlus.State) -> Self { 42 | _bezelStyleState = binding 43 | _setBezelStyle(binding.wrappedValue) 44 | binding.listen { [weak self] in 45 | self?._setBezelStyle($0) 46 | } 47 | return self 48 | } 49 | 50 | @discardableResult 51 | public func style(_ value: NSButton.BezelStyle) -> Self { 52 | _bezelStyleState.wrappedValue = value 53 | _setBezelStyle(value) 54 | return self 55 | } 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /Classes/Protocols/Bezeledable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Bezeledable: AnyObject { 8 | @discardableResult 9 | func bezeled() -> Self 10 | 11 | @discardableResult 12 | func bezeled(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func bezeled(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _Bezeledable: Bezeledable { 19 | func _setBezeled(_ v: Bool) 20 | } 21 | 22 | extension Bezeledable { 23 | @discardableResult 24 | public func bezeled() -> Self { 25 | bezeled(true) 26 | } 27 | 28 | @discardableResult 29 | public func bezeled(_ binding: UIKitPlus.State) -> Self { 30 | binding.listen { [weak self] in 31 | self?.bezeled($0) 32 | } 33 | return bezeled(binding.wrappedValue) 34 | } 35 | } 36 | 37 | @available(iOS 13.0, macOS 10.15, *) 38 | extension Bezeledable { 39 | @discardableResult 40 | public func bezeled(_ value: Bool) -> Self { 41 | guard let s = self as? _Bezeledable else { return self } 42 | s._setBezeled(value) 43 | return self 44 | } 45 | } 46 | 47 | // for iOS lower than 13 48 | extension _Bezeledable { 49 | @discardableResult 50 | public func bezeled(_ value: Bool) -> Self { 51 | _setBezeled(value) 52 | return self 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Classes/Protocols/BodyBuilderItem.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public enum BodyBuilderItem { 8 | case none 9 | case single(BaseView) 10 | case multiple([BaseView]) 11 | case nested([BodyBuilderItemable]) 12 | case forEach(AnyForEach) 13 | } 14 | public protocol BodyBuilderItemable { 15 | var bodyBuilderItem: BodyBuilderItem { get } 16 | } 17 | public struct EmptyBodyBuilderItem: BodyBuilderItemable { 18 | public var bodyBuilderItem: BodyBuilderItem { .none } 19 | } 20 | extension BaseView: BodyBuilderItemable { 21 | public var bodyBuilderItem: BodyBuilderItem { .single(self) } 22 | } 23 | extension Array: BodyBuilderItemable where Element: BaseView { 24 | public var bodyBuilderItem: BodyBuilderItem { .multiple(self) } 25 | } 26 | extension Optional: BodyBuilderItemable where Wrapped: BaseView { 27 | public var bodyBuilderItem: BodyBuilderItem { 28 | switch self { 29 | case .none: return .none 30 | case .some(let value): return .single(value) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Protocols/Borderedable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Borderedable: AnyObject { 8 | @discardableResult 9 | func bordered() -> Self 10 | 11 | @discardableResult 12 | func bordered(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func bordered(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _Borderedable: Borderedable { 19 | var _borderedState: State { get } 20 | 21 | func _setBordered(_ v: Bool) 22 | } 23 | 24 | extension Borderedable { 25 | @discardableResult 26 | public func bordered() -> Self { 27 | bordered(true) 28 | } 29 | 30 | @discardableResult 31 | public func bordered(_ binding: UIKitPlus.State) -> Self { 32 | binding.listen { [weak self] in 33 | self?.bordered($0) 34 | } 35 | return bordered(binding.wrappedValue) 36 | } 37 | } 38 | 39 | @available(iOS 13.0, macOS 10.15, *) 40 | extension Borderedable { 41 | @discardableResult 42 | public func bordered(_ value: Bool) -> Self { 43 | guard let s = self as? _Borderedable else { return self } 44 | s._setBordered(value) 45 | return self 46 | } 47 | } 48 | 49 | // for iOS lower than 13 50 | extension _Borderedable { 51 | @discardableResult 52 | public func bordered(_ value: Bool) -> Self { 53 | _setBordered(value) 54 | return self 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Classes/Protocols/BulletsEchoable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol BulletsEchoable { 8 | @discardableResult 9 | func echosBullets() -> Self 10 | 11 | @discardableResult 12 | func echosBullets(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func echosBullets(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _BulletsEchoable: BulletsEchoable { 19 | func _setEchosBullets(_ v: Bool) 20 | } 21 | 22 | extension BulletsEchoable { 23 | @discardableResult 24 | public func echosBullets() -> Self { 25 | echosBullets(true) 26 | } 27 | 28 | @discardableResult 29 | public func echosBullets(_ binding: UIKitPlus.State) -> Self { 30 | binding.listen { self.echosBullets($0) } 31 | return echosBullets(binding.wrappedValue) 32 | } 33 | } 34 | 35 | @available(iOS 13.0, macOS 10.15, *) 36 | extension BulletsEchoable { 37 | @discardableResult 38 | public func echosBullets(_ value: Bool) -> Self { 39 | guard let s = self as? _BulletsEchoable else { return self } 40 | s._setEchosBullets(value) 41 | return self 42 | } 43 | } 44 | 45 | // for iOS lower than 13 46 | extension _BulletsEchoable { 47 | @discardableResult 48 | public func echosBullets(_ value: Bool) -> Self { 49 | _setEchosBullets(value) 50 | return self 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /Classes/Protocols/Cellable.swift: -------------------------------------------------------------------------------- 1 | public protocol Cellable: AnyObject {} 2 | 3 | extension Cellable { 4 | public static var reuseIdentifier: String { String(describing: self) } 5 | } 6 | -------------------------------------------------------------------------------- /Classes/Protocols/Cleanupable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Cleanupable { 8 | @discardableResult 9 | func cleanup() -> Self 10 | } 11 | 12 | protocol _Cleanupable: Cleanupable { 13 | func _cleanup() 14 | } 15 | 16 | @available(iOS 13.0, *) 17 | extension Cleanupable { 18 | @discardableResult 19 | public func cleanup() -> Self { 20 | guard let s = self as? _Cleanupable else { return self } 21 | s._cleanup() 22 | return self 23 | } 24 | } 25 | 26 | // for iOS lower than 13 27 | extension _Cleanupable { 28 | @discardableResult 29 | public func cleanup() -> Self { 30 | _cleanup() 31 | return self 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Protocols/Colorable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Colorable: AnyObject { 8 | @discardableResult 9 | func color(_ color: UColor) -> Self 10 | 11 | @discardableResult 12 | func color(_ number: Int) -> Self 13 | 14 | @discardableResult 15 | func color(_ color: State) -> Self 16 | } 17 | 18 | protocol _Colorable: Colorable { 19 | var _colorState: State { get } 20 | 21 | #if os(macOS) 22 | func _setColor(_ v: NSColor?) 23 | #else 24 | func _setColor(_ v: UColor?) 25 | #endif 26 | } 27 | 28 | extension Colorable { 29 | @discardableResult 30 | public func color(_ number: Int) -> Self { 31 | color(number.color) 32 | } 33 | 34 | @discardableResult 35 | public func color(_ state: State) -> Self { 36 | state.listen { [weak self] in 37 | self?.color($0) 38 | } 39 | return color(state.wrappedValue) 40 | } 41 | } 42 | 43 | @available(iOS 13.0, *) 44 | extension Colorable { 45 | @discardableResult 46 | public func color(_ color: UColor) -> Self { 47 | guard let s = self as? _Colorable else { return self } 48 | _color(color, on: s) 49 | return self 50 | } 51 | } 52 | 53 | // for iOS lower than 13 54 | extension _Colorable { 55 | @discardableResult 56 | public func color(_ color: UColor) -> Self { 57 | _color(color, on: self) 58 | return self 59 | } 60 | } 61 | 62 | private func _color(_ color: UColor, on s: _Colorable) { 63 | #if os(macOS) 64 | s._colorState.wrappedValue.changeHandler = nil 65 | #endif 66 | s._colorState.wrappedValue = color 67 | s._setColor(color.current) 68 | #if os(macOS) 69 | s._colorState.wrappedValue.onChange { [weak s] new in 70 | s?._setColor(new) 71 | } 72 | #endif 73 | } 74 | -------------------------------------------------------------------------------- /Classes/Protocols/ConstraintValue.swift: -------------------------------------------------------------------------------- 1 | public protocol ConstraintValue { 2 | var constraintValue: ConstraintValueType { get } 3 | } 4 | -------------------------------------------------------------------------------- /Classes/Protocols/Contextable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Contextable { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Classes/Protocols/Continuousable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Continuousable: AnyObject { 8 | @discardableResult 9 | func continuous() -> Self 10 | 11 | @discardableResult 12 | func continuous(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func continuous(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _Continuousable: Continuousable { 19 | var _continuousState: State { get } 20 | 21 | func _setContinuous(_ v: Bool) 22 | } 23 | 24 | extension Continuousable { 25 | @discardableResult 26 | public func continuous() -> Self { 27 | continuous(true) 28 | } 29 | 30 | @discardableResult 31 | public func continuous(_ binding: UIKitPlus.State) -> Self { 32 | binding.listen { [weak self] in 33 | self?.continuous($0) 34 | } 35 | return continuous(binding.wrappedValue) 36 | } 37 | } 38 | 39 | @available(iOS 13.0, macOS 10.15, *) 40 | extension Continuousable { 41 | @discardableResult 42 | public func continuous(_ value: Bool) -> Self { 43 | guard let s = self as? _Continuousable else { return self } 44 | s._setContinuous(value) 45 | return self 46 | } 47 | } 48 | 49 | // for iOS lower than 13 50 | extension _Continuousable { 51 | @discardableResult 52 | public func continuous(_ value: Bool) -> Self { 53 | _setContinuous(value) 54 | return self 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Classes/Protocols/ControlStateable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import Cocoa 3 | 4 | public protocol ControlStateable: AnyObject { 5 | @discardableResult 6 | func state(_ value: NSControl.StateValue) -> Self 7 | 8 | @discardableResult 9 | func state(_ binding: UIKitPlus.State) -> Self 10 | } 11 | 12 | protocol _ControlStateable: ControlStateable { 13 | var _stateState: State { get set } 14 | 15 | func _setState(_ v: NSControl.StateValue) 16 | } 17 | 18 | @available(iOS 13.0, *) 19 | extension ControlStateable { 20 | @discardableResult 21 | public func state(_ binding: UIKitPlus.State) -> Self { 22 | guard var s = self as? _ControlStateable else { return self } 23 | s._stateState = binding 24 | s._setState(binding.wrappedValue) 25 | binding.listen { [weak s] in 26 | s?._setState($0) 27 | } 28 | return self 29 | } 30 | 31 | @discardableResult 32 | public func state(_ value: NSControl.StateValue) -> Self { 33 | guard let s = self as? _ControlStateable else { return self } 34 | s._stateState.wrappedValue = value 35 | s._setState(value) 36 | return self 37 | } 38 | } 39 | 40 | // for iOS lower than 13 41 | extension _ControlStateable { 42 | @discardableResult 43 | public func state(_ binding: UIKitPlus.State) -> Self { 44 | _stateState = binding 45 | _setState(binding.wrappedValue) 46 | binding.listen { [weak self] in 47 | self?._setState($0) 48 | } 49 | return self 50 | } 51 | 52 | @discardableResult 53 | public func state(_ value: NSControl.StateValue) -> Self { 54 | _stateState.wrappedValue = value 55 | _setState(value) 56 | return self 57 | } 58 | } 59 | #endif 60 | -------------------------------------------------------------------------------- /Classes/Protocols/DeclarativeProtocol.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol DeclarativeProtocol: AnyObject { 8 | associatedtype V: BaseView, DeclarativeProtocol = Self 9 | 10 | var declarativeView: V { get } 11 | var properties: Properties { get set } 12 | 13 | // MARK: Public Constraints 14 | 15 | var height: CGFloat { get set } 16 | var width: CGFloat { get set } 17 | var top: CGFloat { get set } 18 | var leading: CGFloat { get set } 19 | var left: CGFloat { get set } 20 | var trailing: CGFloat { get set } 21 | var right: CGFloat { get set } 22 | var bottom: CGFloat { get set } 23 | var centerX: CGFloat { get set } 24 | var centerY: CGFloat { get set } 25 | 26 | var tag: Int { get set } 27 | } 28 | protocol AnyDeclarativeProtocol: DeclarativeProtocol, _BackgroundColorable {} 29 | -------------------------------------------------------------------------------- /Classes/Protocols/DeclarativeProtocolInternal.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | internal protocol DeclarativeProtocolInternal { 8 | var _properties: PropertiesInternal { get set } 9 | 10 | var __height: State { get } 11 | var __width: State { get } 12 | var __top: State { get } 13 | var __leading: State { get } 14 | var __left: State { get } 15 | var __trailing: State { get } 16 | var __right: State { get } 17 | var __bottom: State { get } 18 | var __centerX: State { get } 19 | var __centerY: State { get } 20 | } 21 | -------------------------------------------------------------------------------- /Classes/Protocols/EditableStackView.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | protocol EditableStackView: AnyObject { 8 | var arrangedSubviews: [BaseView] { get } 9 | func addArrangedSubview(_ view: BaseView) 10 | func add(arrangedView: BaseView, at index: Int) 11 | } 12 | 13 | extension EditableStackView { 14 | func add(arrangedView: BaseView, at index: Int) { 15 | let nextViews = arrangedSubviews.dropFirst(index) 16 | nextViews.forEach { $0.removeFromSuperview() } 17 | addArrangedSubview(arrangedView) 18 | nextViews.forEach { self.addArrangedSubview($0) } 19 | } 20 | } 21 | 22 | extension Array where Element == BaseView { 23 | func removeFromSuperview(at indexes: [Int]) { 24 | guard indexes.count > 0 else { return } 25 | var filtered: [BaseView] = [] 26 | enumerated().forEach { i, v in 27 | guard indexes.contains(i) else { return } 28 | filtered.append(v) 29 | } 30 | filtered.forEach { $0.removeFromSuperview() } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Classes/Protocols/Editableable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Editableable: AnyObject { 8 | @discardableResult 9 | func editable() -> Self 10 | 11 | @discardableResult 12 | func editable(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func editable(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _Editableable: Editableable { 19 | func _setEditable(_ v: Bool) 20 | } 21 | 22 | extension Editableable { 23 | @discardableResult 24 | public func editable() -> Self { 25 | editable(true) 26 | } 27 | 28 | @discardableResult 29 | public func editable(_ binding: UIKitPlus.State) -> Self { 30 | binding.listen { [weak self] in 31 | self?.editable($0) 32 | } 33 | return editable(binding.wrappedValue) 34 | } 35 | } 36 | 37 | @available(iOS 13.0, *) 38 | extension Editableable { 39 | @discardableResult 40 | public func editable(_ value: Bool) -> Self { 41 | guard let s = self as? _Editableable else { return self } 42 | s._setEditable(value) 43 | return self 44 | } 45 | } 46 | 47 | // for iOS lower than 13 48 | extension _Editableable { 49 | @discardableResult 50 | public func editable(_ value: Bool) -> Self { 51 | _setEditable(value) 52 | return self 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Classes/Protocols/Enableable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Enableable: AnyObject { 8 | @discardableResult 9 | func enabled() -> Self 10 | 11 | @discardableResult 12 | func enabled(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func enabled(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _Enableable: Enableable { 19 | func _setEnabled(_ v: Bool) 20 | } 21 | 22 | extension Enableable { 23 | @discardableResult 24 | public func enabled() -> Self { 25 | enabled(true) 26 | } 27 | 28 | @discardableResult 29 | public func enabled(_ binding: UIKitPlus.State) -> Self { 30 | binding.listen { [weak self] in 31 | self?.enabled($0) 32 | } 33 | return enabled(binding.wrappedValue) 34 | } 35 | } 36 | 37 | @available(iOS 13.0, *) 38 | extension Enableable { 39 | @discardableResult 40 | public func enabled(_ value: Bool) -> Self { 41 | guard let s = self as? _Enableable else { return self } 42 | s._setEnabled(value) 43 | return self 44 | } 45 | } 46 | 47 | // for iOS lower than 13 48 | extension _Enableable { 49 | @discardableResult 50 | public func enabled(_ value: Bool) -> Self { 51 | _setEnabled(value) 52 | return self 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Classes/Protocols/FirstResponderRefusable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol FirstResponderRefusable: AnyObject { 8 | @discardableResult 9 | func refuseFirstResponder() -> Self 10 | 11 | @discardableResult 12 | func refuseFirstResponder(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func refuseFirstResponder(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _FirstResponderRefusable: FirstResponderRefusable { 19 | var _refuseFirstResponderState: State { get } 20 | 21 | func _setRefuseFirstResponder(_ v: Bool) 22 | } 23 | 24 | extension FirstResponderRefusable { 25 | @discardableResult 26 | public func refuseFirstResponder() -> Self { 27 | refuseFirstResponder(true) 28 | } 29 | 30 | @discardableResult 31 | public func refuseFirstResponder(_ binding: UIKitPlus.State) -> Self { 32 | binding.listen { [weak self] in 33 | self?.refuseFirstResponder($0) 34 | } 35 | return refuseFirstResponder(binding.wrappedValue) 36 | } 37 | } 38 | 39 | @available(iOS 13.0, macOS 10.15, *) 40 | extension FirstResponderRefusable { 41 | @discardableResult 42 | public func refuseFirstResponder(_ value: Bool) -> Self { 43 | guard let s = self as? _FirstResponderRefusable else { return self } 44 | s._setRefuseFirstResponder(value) 45 | return self 46 | } 47 | } 48 | 49 | // for iOS lower than 13 50 | extension _FirstResponderRefusable { 51 | @discardableResult 52 | public func refuseFirstResponder(_ value: Bool) -> Self { 53 | _setRefuseFirstResponder(value) 54 | return self 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Classes/Protocols/FocusRingTypeable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | 4 | public protocol FocusRingTypeable: AnyObject { 5 | @discardableResult 6 | func focusRingType(_ value: NSFocusRingType) -> Self 7 | 8 | @discardableResult 9 | func focusRingType(_ binding: UIKitPlus.State) -> Self 10 | } 11 | 12 | protocol _FocusRingTypeable: FocusRingTypeable { 13 | func _setFocusRingType(_ v: NSFocusRingType) 14 | } 15 | 16 | extension FocusRingTypeable { 17 | @discardableResult 18 | public func focusRingType(_ binding: UIKitPlus.State) -> Self { 19 | binding.listen { [weak self] in 20 | self?.focusRingType($0) 21 | } 22 | return focusRingType(binding.wrappedValue) 23 | } 24 | } 25 | 26 | @available(iOS 13.0, macOS 10.15, *) 27 | extension FocusRingTypeable { 28 | @discardableResult 29 | public func focusRingType(_ value: NSFocusRingType) -> Self { 30 | guard let s = self as? _FocusRingTypeable else { return self } 31 | s._setFocusRingType(value) 32 | return self 33 | } 34 | } 35 | 36 | // for iOS lower than 13 37 | extension _FocusRingTypeable { 38 | @discardableResult 39 | public func focusRingType(_ value: NSFocusRingType) -> Self { 40 | _setFocusRingType(value) 41 | return self 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Classes/Protocols/Fontable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Fontable: AnyObject { 8 | @discardableResult 9 | func font(v: UFont?) -> Self 10 | 11 | @discardableResult 12 | func font(_ identifier: FontIdentifier, _ size: CGFloat) -> Self 13 | } 14 | 15 | protocol _Fontable: Fontable { 16 | func _setFont(_ v: UFont?) 17 | } 18 | 19 | extension Fontable { 20 | @discardableResult 21 | public func font(_ identifier: FontIdentifier, _ size: CGFloat) -> Self { 22 | font(v: UFont(name: identifier.fontName, size: size)) 23 | } 24 | 25 | @discardableResult 26 | public func font(_ binding: State) -> Self { 27 | binding.listen { [weak self] in 28 | self?.font(v: $0) 29 | } 30 | return font(v: binding.wrappedValue) 31 | } 32 | } 33 | 34 | @available(iOS 13.0, *) 35 | extension Fontable { 36 | @discardableResult 37 | public func font(v: UFont?) -> Self { 38 | guard let s = self as? _Fontable else { return self } 39 | s._setFont(v) 40 | return self 41 | } 42 | } 43 | 44 | // for iOS lower than 13 45 | extension _Fontable { 46 | @discardableResult 47 | public func font(v: UFont?) -> Self { 48 | _setFont(v) 49 | return self 50 | } 51 | } 52 | 53 | // MARK: Fontable at range 54 | 55 | public protocol FontableAtRange { 56 | @discardableResult 57 | func font(v: UFont?, at range: ClosedRange) -> Self 58 | 59 | @discardableResult 60 | func font(_ identifier: FontIdentifier, _ size: CGFloat, at range: ClosedRange) -> Self 61 | } 62 | 63 | protocol _FontableAtRange: _Fontable, FontableAtRange {} 64 | 65 | extension FontableAtRange { 66 | @discardableResult 67 | public func font(_ identifier: FontIdentifier, _ size: CGFloat, at range: ClosedRange) -> Self { 68 | font(v: UFont(name: identifier.fontName, size: size), at: range) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Classes/Protocols/Hiddenable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Hiddenable { 8 | @discardableResult 9 | func hidden() -> Self 10 | 11 | @discardableResult 12 | func hidden(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func hidden(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _Hiddenable: Hiddenable { 19 | var _hiddenState: State { get } 20 | 21 | func _setHidden(_ v: Bool) 22 | } 23 | 24 | extension Hiddenable { 25 | @discardableResult 26 | public func hidden() -> Self { 27 | hidden(true) 28 | } 29 | 30 | @discardableResult 31 | public func hidden(_ binding: UIKitPlus.State) -> Self { 32 | binding.listen { self.hidden($0) } 33 | return hidden(binding.wrappedValue) 34 | } 35 | } 36 | 37 | @available(iOS 13.0, macOS 10.15, *) 38 | extension Hiddenable { 39 | @discardableResult 40 | public func hidden(_ value: Bool) -> Self { 41 | guard let s = self as? _Hiddenable else { return self } 42 | s._setHidden(value) 43 | return self 44 | } 45 | } 46 | 47 | // for iOS lower than 13 48 | extension _Hiddenable { 49 | @discardableResult 50 | public func hidden(_ value: Bool) -> Self { 51 | _setHidden(value) 52 | return self 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Classes/Protocols/Identable.swift: -------------------------------------------------------------------------------- 1 | public protocol AnyIdentable { 2 | func identHash() -> Int 3 | } 4 | 5 | public protocol Identable: Hashable, AnyIdentable { 6 | associatedtype ID: Hashable 7 | 8 | typealias IDKey = KeyPath 9 | 10 | static var idKey: IDKey { get } 11 | } 12 | 13 | extension Identable { 14 | func hash(into hasher: inout Hasher) { 15 | hasher.combine(self[keyPath: Self.idKey]) 16 | } 17 | 18 | public func identHash() -> Int { 19 | self[keyPath: Self.idKey].hashValue 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Classes/Protocols/KeyMaskable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyEquivalentMaskable.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 13.08.2020. 6 | // 7 | 8 | #if os(macOS) 9 | import Cocoa 10 | 11 | public protocol KeyMaskable: AnyObject { 12 | @discardableResult 13 | func keyMask(_ mask: NSEvent.ModifierFlags) -> Self 14 | 15 | @discardableResult 16 | func keyMask(_ state: State) -> Self 17 | } 18 | 19 | protocol _KeyMaskable: KeyMaskable { 20 | func _setKeyMask(_ v: NSEvent.ModifierFlags) 21 | } 22 | 23 | extension KeyMaskable { 24 | @discardableResult 25 | public func keyMask(_ mask: NSEvent.ModifierFlags) -> Self { 26 | guard let s = self as? _KeyMaskable else { return self } 27 | s._setKeyMask(mask) 28 | return self 29 | } 30 | 31 | @discardableResult 32 | public func keyMask(_ state: State) -> Self { 33 | keyMask(state.wrappedValue) 34 | state.listen { [weak self] in 35 | self?.keyMask($0) 36 | } 37 | return self 38 | } 39 | } 40 | #endif 41 | -------------------------------------------------------------------------------- /Classes/Protocols/Keyable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import Cocoa 3 | 4 | public protocol Keyable: AnyObject { 5 | @discardableResult 6 | func key(_ text: String) -> Self 7 | 8 | @discardableResult 9 | func key(_ state: State) -> Self 10 | } 11 | 12 | protocol _Keyable: Keyable { 13 | func _setKey(_ v: String) 14 | } 15 | 16 | extension Keyable { 17 | @discardableResult 18 | public func key(_ text: String) -> Self { 19 | guard let s = self as? _Keyable else { return self } 20 | s._setKey(text) 21 | return self 22 | } 23 | 24 | @discardableResult 25 | public func key(_ state: State) -> Self { 26 | key(state.wrappedValue) 27 | state.listen { [weak self] in 28 | self?.key($0) 29 | } 30 | return self 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /Classes/Protocols/Menuable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Menuable.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 22.08.2020. 6 | // 7 | 8 | #if os(macOS) 9 | import AppKit 10 | 11 | public protocol Menuable { 12 | @discardableResult 13 | func menu(_ v: Menu) -> Self 14 | 15 | @discardableResult 16 | func menu(@MenuBuilder content: @escaping MenuBuilder.Block) -> Self 17 | 18 | @discardableResult 19 | func menu(@MenuBuilder content: @escaping (Menu) -> MenuBuilderContent) -> Self 20 | } 21 | 22 | protocol _Menuable: Menuable { 23 | func _setMenu(_ v: Menu) 24 | } 25 | 26 | extension Menuable { 27 | @discardableResult 28 | public func menu(@MenuBuilder content: @escaping MenuBuilder.Block) -> Self { 29 | menu(.init(content: content)) 30 | } 31 | 32 | @discardableResult 33 | public func menu(@MenuBuilder content: @escaping (Menu) -> MenuBuilderContent) -> Self { 34 | let m = Menu() 35 | m.parseMenuBuilder(content(m).menuBuilderContent) 36 | return menu(m) 37 | } 38 | } 39 | 40 | extension Menuable { 41 | @discardableResult 42 | public func menu(_ value: Menu) -> Self { 43 | guard let s = self as? _Menuable else { return self } 44 | s._setMenu(value) 45 | return self 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /Classes/Protocols/MixedStateAllowable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol MixedStateAllowable: AnyObject { 8 | @discardableResult 9 | func allowMixedState() -> Self 10 | 11 | @discardableResult 12 | func allowMixedState(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func allowMixedState(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _MixedStateAllowable: MixedStateAllowable { 19 | var _allowMixedStateState: State { get } 20 | 21 | func _setAllowMixedState(_ v: Bool) 22 | } 23 | 24 | extension MixedStateAllowable { 25 | @discardableResult 26 | public func allowMixedState() -> Self { 27 | allowMixedState(true) 28 | } 29 | 30 | @discardableResult 31 | public func allowMixedState(_ binding: UIKitPlus.State) -> Self { 32 | binding.listen { [weak self] in 33 | self?.allowMixedState($0) 34 | } 35 | return allowMixedState(binding.wrappedValue) 36 | } 37 | } 38 | 39 | @available(iOS 13.0, macOS 10.15, *) 40 | extension MixedStateAllowable { 41 | @discardableResult 42 | public func allowMixedState(_ value: Bool) -> Self { 43 | guard let s = self as? _MixedStateAllowable else { return self } 44 | s._setAllowMixedState(value) 45 | return self 46 | } 47 | } 48 | 49 | // for iOS lower than 13 50 | extension _MixedStateAllowable { 51 | @discardableResult 52 | public func allowMixedState(_ value: Bool) -> Self { 53 | _setAllowMixedState(value) 54 | return self 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Classes/Protocols/MultiClickIgnorable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol MultiClickIgnorable: AnyObject { 8 | @discardableResult 9 | func ignoreMultiClick() -> Self 10 | 11 | @discardableResult 12 | func ignoreMultiClick(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func ignoreMultiClick(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _MultiClickIgnorable: MultiClickIgnorable { 19 | var _ignoreMultiClickState: State { get } 20 | 21 | func _setIgnoreMultiClick(_ v: Bool) 22 | } 23 | 24 | extension MultiClickIgnorable { 25 | @discardableResult 26 | public func ignoreMultiClick() -> Self { 27 | ignoreMultiClick(true) 28 | } 29 | 30 | @discardableResult 31 | public func ignoreMultiClick(_ binding: UIKitPlus.State) -> Self { 32 | binding.listen { [weak self] in 33 | self?.ignoreMultiClick($0) 34 | } 35 | return ignoreMultiClick(binding.wrappedValue) 36 | } 37 | } 38 | 39 | @available(iOS 13.0, macOS 10.15, *) 40 | extension MultiClickIgnorable { 41 | @discardableResult 42 | public func ignoreMultiClick(_ value: Bool) -> Self { 43 | guard let s = self as? _MultiClickIgnorable else { return self } 44 | s._setIgnoreMultiClick(value) 45 | return self 46 | } 47 | } 48 | 49 | // for iOS lower than 13 50 | extension _MultiClickIgnorable { 51 | @discardableResult 52 | public func ignoreMultiClick(_ value: Bool) -> Self { 53 | _setIgnoreMultiClick(value) 54 | return self 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Classes/Protocols/NavigationControllerable.swift: -------------------------------------------------------------------------------- 1 | public protocol NavigationControllerable: AnyObject { 2 | var isSwipeBackEnabled: Bool { get set } 3 | } 4 | -------------------------------------------------------------------------------- /Classes/Protocols/PullsDownable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol PullsDownable: AnyObject { 8 | @discardableResult 9 | func pullsDown() -> Self 10 | 11 | @discardableResult 12 | func pullsDown(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func pullsDown(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _PullsDownable: PullsDownable { 19 | func _setPullsDown(_ v: Bool) 20 | } 21 | 22 | extension PullsDownable { 23 | @discardableResult 24 | public func pullsDown() -> Self { 25 | pullsDown(true) 26 | } 27 | 28 | @discardableResult 29 | public func pullsDown(_ binding: UIKitPlus.State) -> Self { 30 | binding.listen { [weak self] in 31 | self?.pullsDown($0) 32 | } 33 | return pullsDown(binding.wrappedValue) 34 | } 35 | } 36 | 37 | @available(iOS 13.0, macOS 10.15, *) 38 | extension PullsDownable { 39 | @discardableResult 40 | public func pullsDown(_ value: Bool) -> Self { 41 | guard let s = self as? _PullsDownable else { return self } 42 | s._setPullsDown(value) 43 | return self 44 | } 45 | } 46 | 47 | // for iOS lower than 13 48 | extension _PullsDownable { 49 | @discardableResult 50 | public func pullsDown(_ value: Bool) -> Self { 51 | _setPullsDown(value) 52 | return self 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Classes/Protocols/Refreshable.swift: -------------------------------------------------------------------------------- 1 | public protocol Refreshable: AnyObject { 2 | func refresh() 3 | } 4 | 5 | extension Refreshable { 6 | // MARK: Reaction on @State 7 | 8 | @discardableResult 9 | public func react(to a: State) -> Self { 10 | a.listen { [weak self] _,_ in self?.refresh() } 11 | return self 12 | } 13 | 14 | @discardableResult 15 | public func react(to a: State, _ b: State) -> Self { 16 | a.listen { [weak self] _,_ in self?.refresh() } 17 | b.listen { _,_ in self.refresh() } 18 | return self 19 | } 20 | 21 | @discardableResult 22 | public func react(to a: State, _ b: State, _ c: State) -> Self { 23 | a.listen { [weak self] _,_ in self?.refresh() } 24 | b.listen { [weak self] _,_ in self?.refresh() } 25 | c.listen { [weak self] _,_ in self?.refresh() } 26 | return self 27 | } 28 | 29 | @discardableResult 30 | public func react(to a: State, _ b: State, _ c: State, _ d: State) -> Self { 31 | a.listen { [weak self] _,_ in self?.refresh() } 32 | b.listen { [weak self] _,_ in self?.refresh() } 33 | c.listen { [weak self] _,_ in self?.refresh() } 34 | d.listen { [weak self] _,_ in self?.refresh() } 35 | return self 36 | } 37 | 38 | @discardableResult 39 | public func react(to a: State, _ b: State, _ c: State, _ d: State, _ e: State) -> Self { 40 | a.listen { [weak self] _,_ in self?.refresh() } 41 | b.listen { [weak self] _,_ in self?.refresh() } 42 | c.listen { [weak self] _,_ in self?.refresh() } 43 | d.listen { [weak self] _,_ in self?.refresh() } 44 | e.listen { [weak self] _,_ in self?.refresh() } 45 | return self 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Classes/Protocols/Secureable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Secureable: AnyObject { 8 | @discardableResult 9 | func secure() -> Self 10 | 11 | @discardableResult 12 | func secure(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func secure(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _Secureable: Secureable { 19 | func _setSecure(_ v: Bool) 20 | } 21 | 22 | extension Secureable { 23 | @discardableResult 24 | public func secure() -> Self { 25 | secure(true) 26 | } 27 | 28 | @discardableResult 29 | public func secure(_ binding: UIKitPlus.State) -> Self { 30 | binding.listen { [weak self] in 31 | self?.secure($0) 32 | } 33 | return secure(binding.wrappedValue) 34 | } 35 | } 36 | 37 | @available(iOS 13.0, *) 38 | extension Secureable { 39 | @discardableResult 40 | public func secure(_ value: Bool) -> Self { 41 | guard let s = self as? _Secureable else { return self } 42 | s._setSecure(value) 43 | return self 44 | } 45 | } 46 | 47 | // for iOS lower than 13 48 | extension _Secureable { 49 | @discardableResult 50 | public func secure(_ value: Bool) -> Self { 51 | _setSecure(value) 52 | return self 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Classes/Protocols/SegmentControlable.swift: -------------------------------------------------------------------------------- 1 | public protocol SegmentControlable { 2 | var item: SegmentControlableItem { get } 3 | } 4 | -------------------------------------------------------------------------------- /Classes/Protocols/SideViewProtocol.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | protocol SideViewProtocol { 8 | var view: BaseView { get } 9 | } 10 | -------------------------------------------------------------------------------- /Classes/Protocols/Soundable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | 4 | public protocol Soundable: AnyObject { 5 | @discardableResult 6 | func sound(_ value: NSSound?) -> Self 7 | 8 | @discardableResult 9 | func sound(_ binding: UIKitPlus.State) -> Self 10 | } 11 | 12 | protocol _Soundable: Soundable { 13 | func _setSound(_ v: NSSound?) 14 | } 15 | 16 | extension Soundable { 17 | @discardableResult 18 | public func sound(_ binding: UIKitPlus.State) -> Self { 19 | binding.listen { [weak self] in 20 | self?.sound($0) 21 | } 22 | return sound(binding.wrappedValue) 23 | } 24 | } 25 | 26 | @available(iOS 13.0, macOS 10.15, *) 27 | extension Soundable { 28 | @discardableResult 29 | public func sound(_ value: NSSound?) -> Self { 30 | guard let s = self as? _Soundable else { return self } 31 | s._setSound(value) 32 | return self 33 | } 34 | } 35 | 36 | // for iOS lower than 13 37 | extension _Soundable { 38 | @discardableResult 39 | public func sound(_ value: NSSound?) -> Self { 40 | _setSound(value) 41 | return self 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Classes/Protocols/StackForEach.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StackForEach.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 01.01.2020. 6 | // 7 | 8 | protocol StackForEach { 9 | func subscribeToChanges(_ handler: @escaping (_ old: [Any], _ new: [Any], _ deletions: [Int], _ insertions: [Int], _ reloads: [Int]) -> Void) 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Classes/Protocols/Taggable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Taggable.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 17.04.2020. 6 | // 7 | 8 | #if os(macOS) 9 | import AppKit 10 | #else 11 | import UIKit 12 | #endif 13 | 14 | public protocol Taggable: AnyObject { 15 | var tag: Int { get set } 16 | 17 | @discardableResult 18 | func tag(_ value: Int) -> Self 19 | } 20 | 21 | extension Taggable { 22 | @discardableResult 23 | public func tag(_ value: Int) -> Self { 24 | tag = value 25 | return self 26 | } 27 | } 28 | 29 | extension UGestureRecognizer: Taggable { 30 | @objc 31 | public var tag: Int { 32 | get { 0 } 33 | set {} 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Classes/Protocols/TextAdjustsFontSizeable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol TextAdjustsFontSizeable: AnyObject { 8 | @discardableResult 9 | func adjustsFontSizeToFitWidth() -> Self 10 | 11 | @discardableResult 12 | func adjustsFontSizeToFitWidth(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func adjustsFontSizeToFitWidth(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _TextAdjustsFontSizeable: TextAdjustsFontSizeable { 19 | func _setAdjustsFontSizeToFitWidth(_ v: Bool) 20 | } 21 | 22 | extension TextAdjustsFontSizeable { 23 | @discardableResult 24 | public func adjustsFontSizeToFitWidth() -> Self { 25 | adjustsFontSizeToFitWidth(true) 26 | } 27 | 28 | @discardableResult 29 | public func adjustsFontSizeToFitWidth(_ binding: UIKitPlus.State) -> Self { 30 | binding.listen { [weak self] in 31 | self?.adjustsFontSizeToFitWidth($0) 32 | } 33 | return adjustsFontSizeToFitWidth(binding.wrappedValue) 34 | } 35 | } 36 | 37 | @available(iOS 13.0, *) 38 | extension TextAdjustsFontSizeable { 39 | @discardableResult 40 | public func adjustsFontSizeToFitWidth(_ value: Bool) -> Self { 41 | guard let s = self as? _TextAdjustsFontSizeable else { return self } 42 | s._setAdjustsFontSizeToFitWidth(value) 43 | return self 44 | } 45 | } 46 | 47 | // for iOS lower than 13s 48 | extension _TextAdjustsFontSizeable { 49 | @discardableResult 50 | public func adjustsFontSizeToFitWidth(_ value: Bool) -> Self { 51 | _setAdjustsFontSizeToFitWidth(value) 52 | return self 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Classes/Protocols/TextAligmentable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol TextAligmentable { 8 | func alignment(_ alignment: NSTextAlignment) -> Self 9 | } 10 | 11 | protocol _TextAligmentable: TextAligmentable { 12 | func _setTextAlignment(v: NSTextAlignment) 13 | } 14 | 15 | @available(iOS 13.0, *) 16 | extension TextAligmentable { 17 | @discardableResult 18 | public func alignment(_ alignment: NSTextAlignment) -> Self { 19 | guard let s = self as? _TextAligmentable else { return self } 20 | s._setTextAlignment(v: alignment) 21 | return self 22 | } 23 | } 24 | 25 | // for iOS lower than 13 26 | extension _TextAligmentable { 27 | @discardableResult 28 | public func alignment(_ alignment: NSTextAlignment) -> Self { 29 | _setTextAlignment(v: alignment) 30 | return self 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Classes/Protocols/TextAttributesEditingAllowable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol TextAttributesEditingAllowable: AnyObject { 8 | @discardableResult 9 | func allowEditingTextAttributes() -> Self 10 | 11 | @discardableResult 12 | func allowEditingTextAttributes(_ value: Bool) -> Self 13 | 14 | @discardableResult 15 | func allowEditingTextAttributes(_ binding: UIKitPlus.State) -> Self 16 | } 17 | 18 | protocol _TextAttributesEditingAllowable: TextAttributesEditingAllowable { 19 | func _setAllowEditingTextAttributes(_ v: Bool) 20 | } 21 | 22 | extension TextAttributesEditingAllowable { 23 | @discardableResult 24 | public func allowEditingTextAttributes() -> Self { 25 | allowEditingTextAttributes(true) 26 | } 27 | 28 | @discardableResult 29 | public func allowEditingTextAttributes(_ binding: UIKitPlus.State) -> Self { 30 | binding.listen { [weak self] in 31 | self?.allowEditingTextAttributes($0) 32 | } 33 | return allowEditingTextAttributes(binding.wrappedValue) 34 | } 35 | } 36 | 37 | @available(iOS 13.0, macOS 10.15, *) 38 | extension TextAttributesEditingAllowable { 39 | @discardableResult 40 | public func allowEditingTextAttributes(_ value: Bool) -> Self { 41 | guard let s = self as? _TextAttributesEditingAllowable else { return self } 42 | s._setAllowEditingTextAttributes(value) 43 | return self 44 | } 45 | } 46 | 47 | // for iOS lower than 13 48 | extension _TextAttributesEditingAllowable { 49 | @discardableResult 50 | public func allowEditingTextAttributes(_ value: Bool) -> Self { 51 | _setAllowEditingTextAttributes(value) 52 | return self 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Classes/Protocols/TextAutocapitalizationable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public protocol TextAutocapitalizationable { 5 | @discardableResult 6 | func autocapitalization(_ type: UITextAutocapitalizationType) -> Self 7 | } 8 | 9 | protocol _TextAutocapitalizationable: TextAutocapitalizationable { 10 | func _setTextAutocapitalizationType(_ v: UITextAutocapitalizationType) 11 | } 12 | 13 | @available(iOS 13.0, *) 14 | extension TextAutocapitalizationable { 15 | @discardableResult 16 | public func autocapitalization(_ type: UITextAutocapitalizationType) -> Self { 17 | guard let s = self as? _TextAutocapitalizationable else { return self } 18 | s._setTextAutocapitalizationType(type) 19 | return self 20 | } 21 | } 22 | 23 | // for iOS lower than 13 24 | extension _TextAutocapitalizationable { 25 | @discardableResult 26 | public func autocapitalization(_ type: UITextAutocapitalizationType) -> Self { 27 | _setTextAutocapitalizationType(type) 28 | return self 29 | } 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /Classes/Protocols/TextAutocorrectionable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public protocol TextAutocorrectionable { 5 | @discardableResult 6 | func autocorrection(_ type: UITextAutocorrectionType) -> Self 7 | } 8 | 9 | protocol _TextAutocorrectionable: TextAutocorrectionable { 10 | func _setTextAutocorrectionType(_ v: UITextAutocorrectionType) 11 | } 12 | 13 | @available(iOS 13.0, *) 14 | extension TextAutocorrectionable { 15 | @discardableResult 16 | public func autocorrection(_ type: UITextAutocorrectionType) -> Self { 17 | guard let s = self as? _TextAutocorrectionable else { return self } 18 | s._setTextAutocorrectionType(type) 19 | return self 20 | } 21 | } 22 | 23 | // for iOS lower than 13 24 | extension _TextAutocorrectionable { 25 | @discardableResult 26 | public func autocorrection(_ type: UITextAutocorrectionType) -> Self { 27 | _setTextAutocorrectionType(type) 28 | return self 29 | } 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /Classes/Protocols/TextBindable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol TextBindable { 8 | @discardableResult 9 | func bind(_ to: UIKitPlus.State) -> Self 10 | } 11 | 12 | protocol _TextBindable: TextBindable { 13 | func _setTextBind(_ binding: State?) 14 | } 15 | 16 | @available(iOS 13.0, *) 17 | extension TextBindable { 18 | @discardableResult 19 | public func bind(_ to: State) -> Self { 20 | guard let s = self as? _TextBindable else { return self } 21 | s._setTextBind(to) 22 | return self 23 | } 24 | } 25 | 26 | // for iOS lower than 13 27 | extension _TextBindable { 28 | @discardableResult 29 | public func bind(_ to: State) -> Self { 30 | _setTextBind(to) 31 | return self 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Protocols/TextFieldContentTypeable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol TextFieldContentTypeable { 8 | func content(_ content: TextFieldContentType) -> Self 9 | } 10 | 11 | protocol _TextFieldContentTypeable: TextFieldContentTypeable { 12 | func _setTextFieldContentType(v: TextFieldContentType) 13 | } 14 | 15 | @available(iOS 13.0, macOS 10.15, *) 16 | extension TextFieldContentTypeable { 17 | @discardableResult 18 | public func content(_ content: TextFieldContentType) -> Self { 19 | guard let s = self as? _TextFieldContentTypeable else { return self } 20 | s._setTextFieldContentType(v: content) 21 | return self 22 | } 23 | } 24 | 25 | // for iOS lower than 13 26 | extension _TextFieldContentTypeable { 27 | @discardableResult 28 | public func content(_ content: TextFieldContentType) -> Self { 29 | _setTextFieldContentType(v: content) 30 | return self 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Classes/Protocols/TextFieldDelegate.swift: -------------------------------------------------------------------------------- 1 | @objc 2 | public protocol TextFieldDelegate: NSObjectProtocol { 3 | @objc @available(iOS 2.0, *) 4 | optional func textFieldShouldBeginEditing(_ textField: UTextField) -> Bool 5 | 6 | @objc @available(iOS 2.0, *) 7 | optional func textFieldDidBeginEditing(_ textField: UTextField) 8 | 9 | @objc @available(iOS 2.0, *) 10 | optional func textFieldShouldEndEditing(_ textField: UTextField) -> Bool 11 | 12 | @objc @available(iOS 2.0, *) 13 | optional func textFieldDidEndEditing(_ textField: UTextField) 14 | 15 | #if !os(macOS) 16 | @objc @available(iOS 10.0, *) 17 | optional func textFieldDidEndEditing(_ textField: UTextField, reason: UTextField.DidEndEditingReason) 18 | #endif 19 | 20 | @objc @available(iOS 2.0, *) 21 | optional func textField(_ textField: UTextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool 22 | 23 | @objc @available(iOS 2.0, *) 24 | optional func textFieldShouldClear(_ textField: UTextField) -> Bool 25 | 26 | @objc @available(iOS 2.0, *) 27 | optional func textFieldShouldReturn(_ textField: UTextField) -> Bool 28 | } 29 | -------------------------------------------------------------------------------- /Classes/Protocols/TextFieldLeftViewable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public protocol TextFieldLeftViewable { 5 | func leftView(_ view: UIView) -> Self 6 | func leftView(_ view: UIView, mode: UITextField.ViewMode) -> Self 7 | func leftView(_ view: @escaping () -> UIView) -> Self 8 | func leftView(mode: UITextField.ViewMode, _ view: @escaping () -> UIView) -> Self 9 | } 10 | 11 | protocol _TextFieldLeftViewable: TextFieldLeftViewable { 12 | func _setLeftView(v: UIView) 13 | func _setLeftViewMode(v: UITextField.ViewMode) 14 | } 15 | 16 | extension TextFieldLeftViewable { 17 | @discardableResult 18 | public func leftView(_ view: UIView) -> Self { 19 | leftView(view, mode: .always) 20 | } 21 | 22 | @discardableResult 23 | public func leftView(_ view: @escaping () -> UIView) -> Self { 24 | leftView(mode: .always, view) 25 | } 26 | 27 | @discardableResult 28 | public func leftView(mode: UITextField.ViewMode, _ view: @escaping () -> UIView) -> Self { 29 | leftView(view(), mode: mode) 30 | } 31 | } 32 | 33 | @available(iOS 13.0, *) 34 | extension TextFieldLeftViewable { 35 | @discardableResult 36 | public func leftView(_ view: UIView, mode: UITextField.ViewMode) -> Self { 37 | guard let s = self as? _TextFieldLeftViewable else { return self } 38 | s._setLeftView(v: view) 39 | s._setLeftViewMode(v: mode) 40 | return self 41 | } 42 | } 43 | 44 | // for iOS lower than 13 45 | extension _TextFieldLeftViewable { 46 | @discardableResult 47 | public func leftView(_ view: UIView, mode: UITextField.ViewMode) -> Self { 48 | _setLeftView(v: view) 49 | _setLeftViewMode(v: mode) 50 | return self 51 | } 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /Classes/Protocols/TextFieldRightViewable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public protocol TextFieldRightViewable { 5 | func rightView(_ view: UIView) -> Self 6 | func rightView(_ view: UIView, mode: UITextField.ViewMode) -> Self 7 | func rightView(_ view: @escaping () -> UIView) -> Self 8 | func rightView(mode: UITextField.ViewMode, _ view: @escaping () -> UIView) -> Self 9 | } 10 | 11 | protocol _TextFieldRightViewable: TextFieldRightViewable { 12 | func _setRightView(v: UIView) 13 | func _setRightViewMode(v: UITextField.ViewMode) 14 | } 15 | 16 | extension TextFieldRightViewable { 17 | @discardableResult 18 | public func rightView(_ view: UIView) -> Self { 19 | rightView(view, mode: .always) 20 | } 21 | 22 | @discardableResult 23 | public func rightView(_ view: @escaping () -> UIView) -> Self { 24 | rightView(mode: .always, view) 25 | } 26 | 27 | @discardableResult 28 | public func rightView(mode: UITextField.ViewMode, _ view: @escaping () -> UIView) -> Self { 29 | rightView(view(), mode: mode) 30 | } 31 | } 32 | 33 | @available(iOS 13.0, *) 34 | extension TextFieldRightViewable { 35 | @discardableResult 36 | public func rightView(_ view: UIView, mode: UITextField.ViewMode) -> Self { 37 | guard let s = self as? _TextFieldRightViewable else { return self } 38 | s._setRightView(v: view) 39 | s._setRightViewMode(v: mode) 40 | return self 41 | } 42 | } 43 | 44 | // for iOS lower than 13 45 | extension _TextFieldRightViewable { 46 | @discardableResult 47 | public func rightView(_ view: UIView, mode: UITextField.ViewMode) -> Self { 48 | _setRightView(v: view) 49 | _setRightViewMode(v: mode) 50 | return self 51 | } 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /Classes/Protocols/TextLineBreakModeable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol TextLineBreakModeable { 8 | @discardableResult 9 | func lineBreakMode(_ mode: NSLineBreakMode) -> Self 10 | } 11 | 12 | protocol _TextLineBreakModeable: TextLineBreakModeable { 13 | func _setLineBreakMode(_ v: NSLineBreakMode) 14 | } 15 | 16 | @available(iOS 13.0, *) 17 | extension TextLineBreakModeable { 18 | @discardableResult 19 | public func lineBreakMode(_ mode: NSLineBreakMode) -> Self { 20 | guard let s = self as? _TextLineBreakModeable else { return self } 21 | s._setLineBreakMode(mode) 22 | return self 23 | } 24 | } 25 | 26 | // for iOS lower than 13 27 | extension _TextLineBreakModeable { 28 | @discardableResult 29 | public func lineBreakMode(_ mode: NSLineBreakMode) -> Self { 30 | _setLineBreakMode(mode) 31 | return self 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Protocols/TextLineable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol TextLineable { 8 | @discardableResult 9 | func lines(_ number: Int) -> Self 10 | 11 | @discardableResult 12 | func multiline() -> Self 13 | } 14 | 15 | protocol _TextLineable: TextLineable { 16 | func _setNumbelOfLines(_ v: Int) 17 | } 18 | 19 | @available(iOS 13.0, *) 20 | extension TextLineable { 21 | @discardableResult 22 | public func lines(_ number: Int) -> Self { 23 | guard let s = self as? _TextLineable else { return self } 24 | s._setNumbelOfLines(number) 25 | return self 26 | } 27 | 28 | @discardableResult 29 | public func multiline() -> Self { 30 | guard let s = self as? _TextLineable else { return self } 31 | s._setNumbelOfLines(0) 32 | return self 33 | } 34 | } 35 | 36 | // for iOS lower than 13 37 | extension _TextLineable { 38 | @discardableResult 39 | public func lines(_ number: Int) -> Self { 40 | _setNumbelOfLines(number) 41 | return self 42 | } 43 | 44 | @discardableResult 45 | public func multiline() -> Self { 46 | _setNumbelOfLines(0) 47 | return self 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Classes/Protocols/TextScaleable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public protocol TextScaleable { 5 | @discardableResult 6 | func minimumScaleFactor(_ value: CGFloat) -> Self 7 | } 8 | 9 | protocol _TextScaleable: TextScaleable { 10 | func _setMinimumScaleFactor(_ v: CGFloat) 11 | } 12 | 13 | @available(iOS 13.0, *) 14 | extension TextScaleable { 15 | @discardableResult 16 | public func minimumScaleFactor(_ value: CGFloat) -> Self { 17 | guard let s = self as? _TextScaleable else { return self } 18 | s._setMinimumScaleFactor(value) 19 | return self 20 | } 21 | } 22 | 23 | // for iOS lower than 13 24 | extension _TextScaleable { 25 | @discardableResult 26 | public func minimumScaleFactor(_ value: CGFloat) -> Self { 27 | _setMinimumScaleFactor(value) 28 | return self 29 | } 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /Classes/Protocols/Tintable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Tintable: AnyObject { 8 | @discardableResult 9 | func tint(_ color: UColor) -> Self 10 | 11 | @discardableResult 12 | func tint(_ number: Int) -> Self 13 | 14 | @discardableResult 15 | func tint(_ color: State) -> Self 16 | } 17 | 18 | protocol _Tintable: Tintable { 19 | var _tintState: State { get } 20 | 21 | #if os(macOS) 22 | func _setTint(_ v: NSColor?) 23 | #else 24 | func _setTint(_ v: UColor?) 25 | #endif 26 | } 27 | 28 | extension Tintable { 29 | @discardableResult 30 | public func tint(_ number: Int) -> Self { 31 | tint(number.color) 32 | } 33 | 34 | @discardableResult 35 | public func tint(_ state: State) -> Self { 36 | state.listen { [weak self] in 37 | self?.tint($0) 38 | } 39 | return tint(state.wrappedValue) 40 | } 41 | } 42 | 43 | @available(iOS 13.0, *) 44 | extension Tintable { 45 | @discardableResult 46 | public func tint(_ color: UColor) -> Self { 47 | guard let s = self as? _Tintable else { return self } 48 | _tint(color, on: s) 49 | return self 50 | } 51 | } 52 | 53 | // for iOS lower than 13 54 | extension _Tintable { 55 | @discardableResult 56 | public func tint(_ color: UColor) -> Self { 57 | _tint(color, on: self) 58 | return self 59 | } 60 | } 61 | 62 | private func _tint(_ color: UColor, on s: _Tintable) { 63 | #if os(macOS) 64 | s._tintState.wrappedValue.changeHandler = nil 65 | #endif 66 | s._tintState.wrappedValue = color 67 | s._setTint(color.current) 68 | #if os(macOS) 69 | s._tintState.wrappedValue.onChange { [weak s] new in 70 | s?._setTint(new) 71 | } 72 | #endif 73 | } 74 | -------------------------------------------------------------------------------- /Classes/Protocols/Typeable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol Typeable { 8 | @discardableResult 9 | func typing(_ binding: State) -> Self 10 | 11 | @discardableResult 12 | func typing(_ binding: State, _ interval: TimeInterval) -> Self 13 | } 14 | 15 | protocol _Typeable: Typeable { 16 | func _setTypingInterval(_ v: TimeInterval) 17 | func _observeTypingState(_ v: State) 18 | } 19 | 20 | @available(iOS 13.0, *) 21 | extension Typeable { 22 | @discardableResult 23 | public func typing(_ binding: State) -> Self { 24 | guard let s = self as? _Typeable else { return self } 25 | s._observeTypingState(binding) 26 | return self 27 | } 28 | 29 | @discardableResult 30 | public func typing(_ binding: UIKitPlus.State, _ interval: TimeInterval) -> Self { 31 | guard let s = self as? _Typeable else { return self } 32 | s._setTypingInterval(interval) 33 | return typing(binding) 34 | } 35 | } 36 | 37 | // for iOS lower than 13 38 | extension _Typeable { 39 | @discardableResult 40 | public func typing(_ binding: State) -> Self { 41 | _observeTypingState(binding) 42 | return self 43 | } 44 | 45 | @discardableResult 46 | public func typing(_ binding: UIKitPlus.State, _ interval: TimeInterval) -> Self { 47 | _setTypingInterval(interval) 48 | return typing(binding) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Classes/Protocols/UIButtonable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public protocol UIButtonable: UIViewable { 5 | var _button: UIButton { get } 6 | } 7 | 8 | extension UIButtonable { 9 | public var _view: UIView { _button } 10 | } 11 | #endif 12 | -------------------------------------------------------------------------------- /Classes/Protocols/UICollectionViewable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public protocol UICollectionViewable: UIScrollViewable { 5 | var _collectionView: UICollectionView { get } 6 | } 7 | 8 | extension UICollectionViewable { 9 | public var _scrollView: UIView { _collectionView } 10 | } 11 | #endif 12 | -------------------------------------------------------------------------------- /Classes/Protocols/UILabelable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public protocol UILabelable: UIViewable { 5 | var _label: UILabel { get } 6 | } 7 | 8 | extension UILabelable { 9 | public var _view: UIView { _label } 10 | } 11 | #endif 12 | -------------------------------------------------------------------------------- /Classes/Protocols/UIScrollViewable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public protocol UIScrollViewable: UIViewable { 5 | var _scrollView: UIScrollView { get } 6 | } 7 | 8 | extension UIScrollViewable { 9 | public var _view: UIView { _scrollView } 10 | } 11 | #endif 12 | -------------------------------------------------------------------------------- /Classes/Protocols/UITableViewable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public protocol UITableViewable: UIScrollViewable { 5 | var _tableView: UITableView { get } 6 | } 7 | 8 | extension UITableViewable { 9 | public var _scrollView: UIView { _tableView } 10 | } 11 | #endif 12 | -------------------------------------------------------------------------------- /Classes/Protocols/UIViewable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol UIViewable: DeclarativeProtocol { 8 | var _view: BaseView { get } 9 | } 10 | 11 | extension UIViewable { 12 | public var _view: BaseView { declarativeView } 13 | } 14 | -------------------------------------------------------------------------------- /Classes/Protocols/ViewTransitionable.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | protocol _ViewTransitionable { 5 | var _transitionableView: UIView { get } 6 | 7 | func _transition(_ duration: TimeInterval, _ options: UIView.AnimationOptions, animations: @escaping () -> Void) 8 | func _transition(_ duration: TimeInterval, _ options: UIView.AnimationOptions, animations: @escaping () -> Void, completion: (@escaping (Bool) -> Void)) 9 | } 10 | 11 | extension _ViewTransitionable { 12 | func _transition(_ duration: TimeInterval, _ options: UIView.AnimationOptions, animations: @escaping () -> Void, completion: (@escaping (Bool) -> Void)) { 13 | UIView.transition(with: _transitionableView, duration: duration, options: options, animations: animations, completion: completion) 14 | } 15 | 16 | func _transition(_ duration: TimeInterval, _ options: UIView.AnimationOptions, animations: @escaping () -> Void) { 17 | _transition(duration, options, animations: animations) { _ in } 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Classes/Protocols/WrappedViewControllerable.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public protocol WrappedViewControllerable { 8 | var protocolView: UView { get } 9 | var protocolController: BaseViewController? { get } 10 | } 11 | -------------------------------------------------------------------------------- /Classes/Structs/AppBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppBuilder.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 10.09.2020. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public protocol AppBuilderContent { 12 | var appBuilderContent: AppBuilderItem { get } 13 | } 14 | 15 | public enum AppBuilderItem { 16 | case none 17 | case lifecycle(LifecycleBuilderProtocol) 18 | case mainScene(BaseApp.MainScene) 19 | case scene(BaseApp.Scene) 20 | case shortcuts(BaseApp.Shortcuts) 21 | case items([AppBuilderItem]) 22 | } 23 | 24 | struct _AppContent: AppBuilderContent { 25 | let appBuilderContent: AppBuilderItem 26 | } 27 | 28 | @resultBuilder public struct AppBuilder { 29 | public typealias Block = () -> AppBuilderContent 30 | 31 | public static func buildBlock() -> AppBuilderContent { 32 | _AppContent(appBuilderContent: .none) 33 | } 34 | 35 | public static func buildBlock(_ attrs: AppBuilderContent...) -> AppBuilderContent { 36 | buildBlock(attrs) 37 | } 38 | 39 | public static func buildBlock(_ attrs: [AppBuilderContent]) -> AppBuilderContent { 40 | _AppContent(appBuilderContent: .items(attrs.map { $0.appBuilderContent })) 41 | } 42 | 43 | public static func buildIf(_ content: AppBuilderContent?) -> AppBuilderContent { 44 | guard let content = content else { return _AppContent(appBuilderContent: .none) } 45 | return _AppContent(appBuilderContent: .items([content.appBuilderContent])) 46 | } 47 | 48 | public static func buildEither(first: AppBuilderContent) -> AppBuilderContent { 49 | _AppContent(appBuilderContent: .items([first.appBuilderContent])) 50 | } 51 | 52 | public static func buildEither(second: AppBuilderContent) -> AppBuilderContent { 53 | _AppContent(appBuilderContent: .items([second.appBuilderContent])) 54 | } 55 | } 56 | #endif 57 | -------------------------------------------------------------------------------- /Classes/Structs/BodyBuilder.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | @available(*, deprecated, renamed: "BodyBuilder") 8 | public typealias ViewBuilder = BodyBuilder 9 | 10 | @resultBuilder public struct BodyBuilder { 11 | public typealias Result = BodyBuilderItemable 12 | public typealias SingleView = () -> Result 13 | 14 | /// Builds an empty view from an block containing no statements, `{ }`. 15 | public static func buildBlock() -> Result { [] } 16 | 17 | /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through unmodified. 18 | public static func buildBlock(_ attrs: BodyBuilderItemable...) -> Result { 19 | buildBlock(attrs) 20 | } 21 | 22 | /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through unmodified. 23 | public static func buildBlock(_ attrs: [BodyBuilderItemable]) -> Result { 24 | BodyBuilderItems(items: attrs) 25 | } 26 | 27 | /// Provides support for "if" statements in multi-statement closures, producing an `Optional` view 28 | /// that is visible only when the `if` condition evaluates `true`. 29 | public static func buildIf(_ content: BodyBuilderItemable?) -> Result { 30 | guard let content = content else { return EmptyBodyBuilderItem() } 31 | return content 32 | } 33 | 34 | /// Provides support for "if" statements in multi-statement closures, producing 35 | /// ConditionalContent for the "then" branch. 36 | public static func buildEither(first: BodyBuilderItemable) -> Result { 37 | first 38 | } 39 | 40 | /// Provides support for "if-else" statements in multi-statement closures, producing 41 | /// ConditionalContent for the "else" branch. 42 | public static func buildEither(second: BodyBuilderItemable) -> Result { 43 | second 44 | } 45 | } 46 | 47 | extension BaseViewController: BodyBuilderItemable { 48 | public var bodyBuilderItem: BodyBuilderItem { 49 | .single(UView(inline: view).edgesToSuperview()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Classes/Structs/BodyBuilderItems.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public struct BodyBuilderItems: BodyBuilderItemable { 8 | var items: [BodyBuilderItemable] = [] 9 | 10 | public var bodyBuilderItem: BodyBuilderItem { 11 | .nested(items) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Classes/Structs/Borders.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | public class Borders { 8 | public enum Side { 9 | case top, left, right, bottom 10 | } 11 | 12 | var views: [Side: UView] = [:] 13 | } 14 | -------------------------------------------------------------------------------- /Classes/Structs/CodableState.swift: -------------------------------------------------------------------------------- 1 | @propertyWrapper 2 | public class CodableState: Stateable, Codable, Equatable, Hashable where Value: Codable, Value: Hashable { 3 | public var wrappedValue: Value { 4 | get { projectedValue.wrappedValue } 5 | set { projectedValue.wrappedValue = newValue } 6 | } 7 | 8 | public var projectedValue: State 9 | 10 | public init(wrappedValue value: Value) { 11 | projectedValue = .init(wrappedValue: value) 12 | } 13 | 14 | required public init (from decoder: Decoder) throws { 15 | let value = try decoder.singleValueContainer().decode(Value.self) 16 | projectedValue = .init(wrappedValue: value) 17 | } 18 | 19 | public func encode(to encoder: Encoder) throws { 20 | var single = encoder.singleValueContainer() 21 | try single.encode(wrappedValue) 22 | } 23 | 24 | public func reset() { 25 | projectedValue.reset() 26 | } 27 | 28 | public func beginTrigger(_ trigger: @escaping State.Trigger) { 29 | projectedValue.beginTrigger(trigger) 30 | } 31 | 32 | public func endTrigger(_ trigger: @escaping State.Trigger) { 33 | projectedValue.endTrigger(trigger) 34 | } 35 | 36 | public func listen(_ listener: @escaping State.Listener) { 37 | projectedValue.listen(listener) 38 | } 39 | 40 | public func listen(_ listener: @escaping State.SimpleListener) { 41 | projectedValue.listen(listener) 42 | } 43 | 44 | public func listen(_ listener: @escaping () -> Void) { 45 | projectedValue.listen(listener) 46 | } 47 | 48 | /// See `Equatable` 49 | 50 | public static func == (lhs: CodableState, rhs: CodableState) -> Bool { 51 | lhs.wrappedValue == rhs.wrappedValue 52 | } 53 | 54 | /// See `Hashable` 55 | 56 | public func hash(into hasher: inout Hasher) { 57 | hasher.combine(wrappedValue) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Classes/Structs/ConstraintValueType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(macOS) 3 | import AppKit 4 | #else 5 | import UIKit 6 | #endif 7 | 8 | public struct ConstraintValueType: ConstraintValue { 9 | public var constraintValue: ConstraintValueType { self } 10 | 11 | let relation: NSLayoutConstraint.Relation 12 | let value, multiplier: CGFloat 13 | let priority: UILayoutPriority 14 | 15 | init (_ mode: NSLayoutConstraint.Relation = .equal, 16 | _ value: CGFloat, 17 | _ multiplier: CGFloat = 1, 18 | _ priority: UILayoutPriority = .init(1000)) { 19 | self.relation = mode 20 | self.value = value 21 | self.multiplier = multiplier 22 | self.priority = priority 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Classes/Structs/CustomCorners.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | public class CustomCorners { 5 | let radius: CGFloat 6 | let corners: [UIRectCorner] 7 | var backgroundColor: UIColor? 8 | 9 | init (radius: CGFloat, corners: [UIRectCorner]) { 10 | self.radius = radius 11 | self.corners = corners 12 | } 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /Classes/Structs/ExpressableState.swift: -------------------------------------------------------------------------------- 1 | extension State { 2 | public func map(_ expression: @escaping () -> Result) -> State { 3 | .init(self, expression) 4 | } 5 | 6 | public func map(_ expression: @escaping (Value) -> Result) -> State { 7 | .init(self, expression) 8 | } 9 | } 10 | 11 | // MARK: Any States to Expressable 12 | 13 | public protocol AnyState: AnyObject { 14 | func listen(_ listener: @escaping () -> Void) 15 | } 16 | 17 | public class AnyStates { 18 | private var _expression: (() -> Void)? 19 | 20 | @discardableResult 21 | init (_ states: [AnyState], expression: @escaping () -> Void) { 22 | _expression = expression 23 | for state in states { 24 | state.listen { [weak self] in 25 | self?._expression?() 26 | } 27 | } 28 | } 29 | } 30 | 31 | extension Array where Element == AnyState { 32 | public func map(_ expression: @escaping () -> Result) -> State { 33 | let state = State.init(wrappedValue: expression()) 34 | AnyStates(self) { [weak state] in 35 | state?.wrappedValue = expression() 36 | } 37 | return state 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Classes/Structs/GesturesBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GesturesBuilder.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 17.04.2020. 6 | // 7 | 8 | import Foundation 9 | 10 | @resultBuilder public struct GesturesBuilder { 11 | public typealias Block = () -> GesturesBuilderItem 12 | 13 | /// Builds an empty preview from an block containing no statements, `{ }`. 14 | public static func buildBlock() -> GesturesBuilderItem { [] } 15 | 16 | /// Passes a single preview written as a child view (e..g, `{ Text("Hello") }`) through unmodified. 17 | public static func buildBlock(_ attrs: GesturesBuilderItem...) -> GesturesBuilderItem { 18 | buildBlock(attrs) 19 | } 20 | 21 | /// Passes a single preview written as a child view (e..g, `{ Text("Hello") }`) through unmodified. 22 | public static func buildBlock(_ attrs: [GesturesBuilderItem]) -> GesturesBuilderItem { 23 | attrs.flatMap { $0.gestureRecognizers } 24 | } 25 | 26 | /// Provides support for "if" statements in multi-statement closures, producing an `Optional` preview 27 | /// that is visible only when the `if` condition evaluates `true`. 28 | public static func buildIf(_ content: GesturesBuilderItem?) -> GesturesBuilderItem { 29 | guard let content = content else { return [] } 30 | return content 31 | } 32 | 33 | /// Provides support for "if" statements in multi-statement closures, producing 34 | /// ConditionalContent for the "then" branch. 35 | public static func buildEither(first: GesturesBuilderItem) -> GesturesBuilderItem { 36 | first 37 | } 38 | 39 | /// Provides support for "if-else" statements in multi-statement closures, producing 40 | /// ConditionalContent for the "else" branch. 41 | public static func buildEither(second: GesturesBuilderItem) -> GesturesBuilderItem { 42 | second 43 | } 44 | } 45 | 46 | public protocol GesturesBuilderItem { 47 | var gestureRecognizers: [UGestureRecognizer] { get } 48 | } 49 | 50 | extension UGestureRecognizer: GesturesBuilderItem { 51 | public var gestureRecognizers: [UGestureRecognizer] { [self] } 52 | } 53 | 54 | extension Array: GesturesBuilderItem where Element: UGestureRecognizer { 55 | public var gestureRecognizers: [UGestureRecognizer] { self } 56 | } 57 | -------------------------------------------------------------------------------- /Classes/Structs/ImageReloadingStyle.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct ImageReloadingStyle: Equatable { 4 | let value: Int 5 | 6 | public init (_ value: Int) { 7 | self.value = value 8 | } 9 | 10 | /// Releases image to nil or default before loading the new one 11 | public static var release: ImageReloadingStyle { .init(0) } 12 | 13 | /// Immediately changes image to the new one 14 | public static var immediate: ImageReloadingStyle { .init(1) } 15 | 16 | /// Shows next image through the fade animation 17 | public static var fade: ImageReloadingStyle { .init(2) } 18 | 19 | // MARK: Equatable 20 | 21 | public static func == (lhs: ImageReloadingStyle, rhs: ImageReloadingStyle) -> Bool { 22 | lhs.value == rhs.value 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Classes/Structs/InnerState.swift: -------------------------------------------------------------------------------- 1 | @propertyWrapper 2 | public class InnerState { 3 | private var _keyPath: WritableKeyPath 4 | private var _wrappedValue: State 5 | public var wrappedValue: InnerValue { 6 | get { _wrappedValue.wrappedValue[keyPath: _keyPath] } 7 | set { 8 | let oldValue = _wrappedValue.wrappedValue[keyPath: _keyPath] 9 | _wrappedValue.wrappedValue[keyPath: _keyPath] = newValue 10 | listeners.forEach { $0(oldValue, newValue) } 11 | } 12 | } 13 | 14 | @State 15 | var innerState: InnerValue 16 | 17 | public var projectedValue: State { $innerState } 18 | 19 | public init(_ value: State, _ keyPath: WritableKeyPath) { 20 | _wrappedValue = value 21 | _keyPath = keyPath 22 | innerState = _wrappedValue.wrappedValue[keyPath: keyPath] 23 | value.listen { [weak self, weak keyPath] newValue in 24 | guard let keyPath = keyPath else { return } 25 | self?.innerState = newValue[keyPath: keyPath] 26 | } 27 | $innerState.listen { [weak self] oldValue, newValue in 28 | self?.listeners.forEach { $0(oldValue, newValue) } 29 | } 30 | } 31 | 32 | public typealias Listener = (_ old: InnerValue, _ new: InnerValue) -> Void 33 | public typealias SimpleListener = (_ value: InnerValue) -> Void 34 | 35 | private var listeners: [Listener] = [] 36 | 37 | public func listen(_ listener: @escaping Listener) { 38 | listeners.append(listener) 39 | } 40 | 41 | public func listen(_ listener: @escaping SimpleListener) { 42 | listeners.append({ _, new in listener(new) }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Classes/Structs/PreviewBuilderItem.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | #if canImport(SwiftUI) && DEBUG 8 | @available(iOS 13.0, macOS 10.15, *) 9 | public protocol PreviewBuilderItem { 10 | var previewBuilderItems: [Preview] { get } 11 | } 12 | 13 | @available(iOS 13.0, macOS 10.15, *) 14 | public struct PreviewBuilderItems: PreviewBuilderItem { 15 | public let items: [Preview] 16 | 17 | public init (items: [Preview]) { 18 | self.items = items 19 | } 20 | 21 | public var previewBuilderItems: [Preview] { items } 22 | } 23 | 24 | @available(iOS 13.0, macOS 10.15, *) 25 | extension Preview: PreviewBuilderItem { 26 | public var previewBuilderItems: [Preview] { [self] } 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /Classes/Structs/ShortcutBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShortcutBuilder.swift 3 | // UIKit-Plus 4 | // 5 | // Created by Mihael Isaev on 11.09.2020. 6 | // 7 | 8 | #if !os(macOS) 9 | import UIKit 10 | 11 | public protocol ShortcutBuilderContent { 12 | var shortcutBuilderContent: ShortcutBuilderItem { get } 13 | } 14 | 15 | public enum ShortcutBuilderItem { 16 | case none 17 | case shortcut(BaseApp.Shortcut) 18 | case items([ShortcutBuilderItem]) 19 | } 20 | 21 | struct _ShortcutContent: ShortcutBuilderContent { 22 | let shortcutBuilderContent: ShortcutBuilderItem 23 | } 24 | 25 | @resultBuilder public struct ShortcutBuilder { 26 | public typealias Block = () -> ShortcutBuilderContent 27 | 28 | public static func buildBlock() -> ShortcutBuilderContent { 29 | _ShortcutContent(shortcutBuilderContent: .none) 30 | } 31 | 32 | public static func buildBlock(_ attrs: ShortcutBuilderContent...) -> ShortcutBuilderContent { 33 | buildBlock(attrs) 34 | } 35 | 36 | public static func buildBlock(_ attrs: [ShortcutBuilderContent]) -> ShortcutBuilderContent { 37 | _ShortcutContent(shortcutBuilderContent: .items(attrs.map { $0.shortcutBuilderContent })) 38 | } 39 | 40 | public static func buildIf(_ content: ShortcutBuilderContent?) -> ShortcutBuilderContent { 41 | guard let content = content else { return _ShortcutContent(shortcutBuilderContent: .none) } 42 | return _ShortcutContent(shortcutBuilderContent: .items([content.shortcutBuilderContent])) 43 | } 44 | 45 | public static func buildEither(first: ShortcutBuilderContent) -> ShortcutBuilderContent { 46 | _ShortcutContent(shortcutBuilderContent: .items([first.shortcutBuilderContent])) 47 | } 48 | 49 | public static func buildEither(second: ShortcutBuilderContent) -> ShortcutBuilderContent { 50 | _ShortcutContent(shortcutBuilderContent: .items([second.shortcutBuilderContent])) 51 | } 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /Classes/Structs/StateStringBuilder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @resultBuilder public struct AnyStringBuilder { 4 | public typealias Handler = () -> AnyString 5 | 6 | public static func buildBlock() -> AnyString { "" } 7 | 8 | public static func buildBlock(_ string: AnyString...) -> AnyString { 9 | buildBlock(string) 10 | } 11 | 12 | public static func buildBlock(_ string: [AnyString]) -> AnyString { 13 | AttrStr(anyStrings: string) 14 | } 15 | 16 | public static func buildIf(_ content: AnyString?) -> AnyString { 17 | guard let content = content else { return "" } 18 | return content 19 | } 20 | 21 | public static func buildEither(first: AnyString) -> AnyString { 22 | first 23 | } 24 | 25 | public static func buildEither(second: AnyString) -> AnyString { 26 | second 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Classes/Structs/UILayoutPriority.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | 4 | public struct UILayoutPriority : Hashable, Equatable, RawRepresentable { 5 | public var rawValue: Float 6 | 7 | public init(_ rawValue: Float) { self.rawValue = rawValue } 8 | public init(rawValue: Float) { self.rawValue = rawValue } 9 | } 10 | extension UILayoutPriority { 11 | // This is the priority level with which a button resists compressing its content. 12 | public static var defaultHigh: UILayoutPriority { .init(rawValue: 1000) } 13 | 14 | // This is the priority level at which a button hugs its contents horizontally. 15 | public static var defaultLow: UILayoutPriority { .init(rawValue: 10) } 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /Classes/Structs/ViewContext.swift: -------------------------------------------------------------------------------- 1 | // Global context for any view 2 | 3 | #if os(macOS) 4 | import AppKit 5 | #else 6 | import UIKit 7 | #endif 8 | 9 | public struct ViewContext { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Classes/Views/MacOS/SecureTextField.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import Foundation 3 | import AppKit 4 | 5 | open class USecureTextField: UTextField { 6 | override func _setup() { 7 | cell = NSSecureTextFieldCell(textCell: attributedStringValue.string) 8 | isEditable = true 9 | super._setup() 10 | } 11 | } 12 | 13 | //extension USecureTextField: _Secureable { 14 | // func _setSecure(_ v: Bool) { 15 | // var isEditable = self.isEditable 16 | // guard let cell = cell as? NSTextFieldCell else { return } 17 | // let c: NSTextFieldCell 18 | // if v { 19 | // c = NSSecureTextFieldCell(textCell: attributedStringValue.string) 20 | // } else { 21 | // c = NSTextFieldCell(textCell: attributedStringValue.string) 22 | // } 23 | // c.backgroundColor = cell.backgroundColor 24 | // c.drawsBackground = cell.drawsBackground 25 | // c.textColor = cell.textColor 26 | // c.bezelStyle = cell.bezelStyle 27 | // c.placeholderString = cell.placeholderString 28 | // c.placeholderAttributedString = cell.placeholderAttributedString 29 | // c.allowedInputSourceLocales = cell.allowedInputSourceLocales 30 | // self.cell = c 31 | // self.isEditable = isEditable 32 | // 33 | //// resignFirstResponder() 34 | //// abortEditing() 35 | //// becomeFirstResponder() 36 | // 37 | //// setNeedsDisplay() 38 | //// layer?.layoutIfNeeded() 39 | // 40 | // } 41 | //} 42 | 43 | extension USecureTextField: _BulletsEchoable { 44 | func _setEchosBullets(_ v: Bool) { 45 | (cell as? NSSecureTextFieldCell)?.echosBullets = v 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /Classes/Views/Not-MacOS/CollectionDynamicCell.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | class UCollectionDynamicCell: CollectionViewCell { 5 | func setRootView(_ rootView: UStackView) { 6 | contentView.subviews.forEach { $0.removeFromSuperview() } 7 | rootView.translatesAutoresizingMaskIntoConstraints = false 8 | contentView.body { rootView } 9 | NSLayoutConstraint.activate([ 10 | contentView.leadingAnchor.constraint(equalTo: rootView.leadingAnchor), 11 | contentView.trailingAnchor.constraint(equalTo: rootView.trailingAnchor), 12 | contentView.topAnchor.constraint(equalTo: rootView.topAnchor), 13 | contentView.bottomAnchor.constraint(equalTo: rootView.bottomAnchor) 14 | ]) 15 | } 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /Classes/Views/Not-MacOS/CollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | @available(*, deprecated, renamed: "UCollectionCell") 5 | public typealias CollectionViewCell = UCollectionCell 6 | 7 | @available(*, deprecated, renamed: "UCollectionCell") 8 | public typealias UCollectionViewCell = UCollectionCell 9 | 10 | open class UCollectionCell: UICollectionViewCell, AnyDeclarativeProtocol, DeclarativeProtocolInternal, Cellable { 11 | public var declarativeView: UCollectionCell { self } 12 | public lazy var properties = Properties() 13 | lazy var _properties = PropertiesInternal() 14 | 15 | @State public var height: CGFloat = 0 16 | @State public var width: CGFloat = 0 17 | @State public var top: CGFloat = 0 18 | @State public var leading: CGFloat = 0 19 | @State public var left: CGFloat = 0 20 | @State public var trailing: CGFloat = 0 21 | @State public var right: CGFloat = 0 22 | @State public var bottom: CGFloat = 0 23 | @State public var centerX: CGFloat = 0 24 | @State public var centerY: CGFloat = 0 25 | 26 | var __height: State { _height } 27 | var __width: State { _width } 28 | var __top: State { _top } 29 | var __leading: State { _leading } 30 | var __left: State { _left } 31 | var __trailing: State { _trailing } 32 | var __right: State { _right } 33 | var __bottom: State { _bottom } 34 | var __centerX: State { _centerX } 35 | var __centerY: State { _centerY } 36 | 37 | public override init(frame: CGRect) { 38 | super.init(frame: frame) 39 | buildView() 40 | } 41 | 42 | public required init?(coder aDecoder: NSCoder) { 43 | super.init(coder: aDecoder) 44 | buildView() 45 | } 46 | 47 | open func buildView() { 48 | backgroundColor = .clear 49 | contentView.backgroundColor = .clear 50 | } 51 | 52 | open override func layoutSubviews() { 53 | super.layoutSubviews() 54 | onLayoutSubviews() 55 | } 56 | 57 | open override func didMoveToSuperview() { 58 | super.didMoveToSuperview() 59 | movedToSuperview() 60 | } 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /Classes/Views/Not-MacOS/ControlView.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | @available(*, deprecated, renamed: "UControlView") 5 | public typealias ControlView = UControlView 6 | 7 | open class UControlView: UIControl, AnyDeclarativeProtocol, DeclarativeProtocolInternal { 8 | public var declarativeView: ControlView { self } 9 | public lazy var properties = Properties() 10 | lazy var _properties = PropertiesInternal() 11 | 12 | @UIKitPlus.State public var height: CGFloat = 0 13 | @UIKitPlus.State public var width: CGFloat = 0 14 | @UIKitPlus.State public var top: CGFloat = 0 15 | @UIKitPlus.State public var leading: CGFloat = 0 16 | @UIKitPlus.State public var left: CGFloat = 0 17 | @UIKitPlus.State public var trailing: CGFloat = 0 18 | @UIKitPlus.State public var right: CGFloat = 0 19 | @UIKitPlus.State public var bottom: CGFloat = 0 20 | @UIKitPlus.State public var centerX: CGFloat = 0 21 | @UIKitPlus.State public var centerY: CGFloat = 0 22 | 23 | var __height: UIKitPlus.State { $height } 24 | var __width: UIKitPlus.State { $width } 25 | var __top: UIKitPlus.State { $top } 26 | var __leading: UIKitPlus.State { $leading } 27 | var __left: UIKitPlus.State { $left } 28 | var __trailing: UIKitPlus.State { $trailing } 29 | var __right: UIKitPlus.State { $right } 30 | var __bottom: UIKitPlus.State { $bottom } 31 | var __centerX: UIKitPlus.State { $centerX } 32 | var __centerY: UIKitPlus.State { $centerY } 33 | 34 | public override init(frame: CGRect) { 35 | super.init(frame: frame) 36 | translatesAutoresizingMaskIntoConstraints = false 37 | buildView() 38 | } 39 | 40 | required public init?(coder aDecoder: NSCoder) { 41 | fatalError("init(coder:) has not been implemented") 42 | } 43 | 44 | open func buildView() {} 45 | 46 | open override func layoutSubviews() { 47 | super.layoutSubviews() 48 | onLayoutSubviews() 49 | } 50 | 51 | open override func didMoveToSuperview() { 52 | super.didMoveToSuperview() 53 | movedToSuperview() 54 | } 55 | } 56 | #endif 57 | -------------------------------------------------------------------------------- /Classes/Views/Not-MacOS/LayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Credits to Florian Friedrich https://gist.github.com/ffried/2e1176e302f8f37100b1eb00cb5f2b7d 3 | // 4 | 5 | #if !os(macOS) 6 | import UIKit 7 | 8 | class ULayerView: UView { 9 | override final class var layerClass: AnyClass { 10 | Layer.self 11 | } 12 | 13 | final var concreteLayer: Layer { 14 | layer as! Layer 15 | } 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /Classes/Views/Not-MacOS/ListDynamicCell.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | class UListDynamicCell: TableViewCell { 5 | func setRootView(_ rootView: UStackView) { 6 | contentView.subviews.forEach { $0.removeFromSuperview() } 7 | rootView.translatesAutoresizingMaskIntoConstraints = false 8 | contentView.body { rootView } 9 | NSLayoutConstraint.activate([ 10 | contentView.leadingAnchor.constraint(equalTo: rootView.leadingAnchor), 11 | contentView.trailingAnchor.constraint(equalTo: rootView.trailingAnchor), 12 | contentView.topAnchor.constraint(equalTo: rootView.topAnchor), 13 | contentView.bottomAnchor.constraint(equalTo: rootView.bottomAnchor) 14 | ]) 15 | } 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /Classes/Views/Not-MacOS/StaticListCell.swift: -------------------------------------------------------------------------------- 1 | //#if !os(macOS) 2 | //import UIKit 3 | // 4 | //class UStaticListCell: TableViewCell { 5 | // init (_ rootView: UIView) { 6 | // super.init(style: .default, reuseIdentifier: nil) 7 | // contentView.body { rootView } 8 | // NSLayoutConstraint.activate([ 9 | // contentView.leadingAnchor.constraint(equalTo: rootView.leadingAnchor), 10 | // contentView.trailingAnchor.constraint(equalTo: rootView.trailingAnchor), 11 | // contentView.topAnchor.constraint(equalTo: rootView.topAnchor), 12 | // contentView.bottomAnchor.constraint(equalTo: rootView.bottomAnchor) 13 | // ]) 14 | // } 15 | // 16 | // public required init?(coder aDecoder: NSCoder) { 17 | // fatalError("init(coder:) has not been implemented") 18 | // } 19 | //} 20 | //#endif 21 | -------------------------------------------------------------------------------- /Classes/Views/Not-MacOS/TableViewCell.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | @available(*, deprecated, renamed: "UTableViewCell") 5 | public typealias TableViewCell = UTableViewCell 6 | 7 | open class UTableViewCell: UITableViewCell, AnyDeclarativeProtocol, DeclarativeProtocolInternal, Cellable { 8 | public var declarativeView: UTableViewCell { self } 9 | public lazy var properties = Properties() 10 | lazy var _properties = PropertiesInternal() 11 | 12 | @State public var height: CGFloat = 0 13 | @State public var width: CGFloat = 0 14 | @State public var top: CGFloat = 0 15 | @State public var leading: CGFloat = 0 16 | @State public var left: CGFloat = 0 17 | @State public var trailing: CGFloat = 0 18 | @State public var right: CGFloat = 0 19 | @State public var bottom: CGFloat = 0 20 | @State public var centerX: CGFloat = 0 21 | @State public var centerY: CGFloat = 0 22 | 23 | var __height: State { _height } 24 | var __width: State { _width } 25 | var __top: State { _top } 26 | var __leading: State { _leading } 27 | var __left: State { _left } 28 | var __trailing: State { _trailing } 29 | var __right: State { _right } 30 | var __bottom: State { _bottom } 31 | var __centerX: State { _centerX } 32 | var __centerY: State { _centerY } 33 | 34 | public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 35 | super.init(style: style, reuseIdentifier: reuseIdentifier) 36 | buildView() 37 | } 38 | 39 | public required init?(coder aDecoder: NSCoder) { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | 43 | open func buildView() { 44 | backgroundColor = .clear 45 | contentView.backgroundColor = .clear 46 | } 47 | 48 | open override func layoutSubviews() { 49 | super.layoutSubviews() 50 | onLayoutSubviews() 51 | } 52 | 53 | open override func didMoveToSuperview() { 54 | super.didMoveToSuperview() 55 | movedToSuperview() 56 | } 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /Classes/Views/Not-MacOS/WrappedViewControllerView.swift: -------------------------------------------------------------------------------- 1 | #if !os(macOS) 2 | import UIKit 3 | 4 | @available(*, deprecated, renamed: "UWrappedViewControllerView") 5 | public typealias WrappedViewControllerView = UWrappedViewControllerView 6 | 7 | open class UWrappedViewControllerView: UView, WrappedViewControllerable where V: UIViewController { 8 | public weak var inner: V? 9 | public weak var parent: UIViewController? 10 | 11 | public var protocolView: UView { return self } 12 | public var protocolController: UIViewController? { inner } 13 | 14 | public init (_ inner: V, parent: UIViewController) { 15 | self.inner = inner 16 | self.parent = parent 17 | parent.addChild(inner) 18 | super.init(frame: .zero) 19 | inner.didMove(toParent: parent) 20 | inner.view.translatesAutoresizingMaskIntoConstraints = false 21 | body { inner.view } 22 | inner.view.topAnchor.constraint(equalTo: topAnchor).activated() 23 | inner.view.leftAnchor.constraint(equalTo: leftAnchor).activated() 24 | inner.view.rightAnchor.constraint(equalTo: rightAnchor).activated() 25 | inner.view.bottomAnchor.constraint(equalTo: bottomAnchor).activated() 26 | } 27 | 28 | required public init?(coder aDecoder: NSCoder) { 29 | fatalError("init(coder:) has not been implemented") 30 | } 31 | 32 | open override func layoutSubviews() { 33 | super.layoutSubviews() 34 | inner?.view.layer.masksToBounds = true 35 | guard let _ = _declarativeView._properties.customCorners else { 36 | inner?.view.layer.cornerRadius = layer.cornerRadius 37 | return 38 | } 39 | inner?.view.layer.cornerRadius = 0 40 | inner?.view.layer.mask = layer.sublayers?.first 41 | } 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /Classes/Views/Universal/BaseView.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | 4 | public typealias BaseView = NSView 5 | #else 6 | import UIKit 7 | 8 | public typealias BaseView = UIView 9 | #endif 10 | -------------------------------------------------------------------------------- /Classes/Views/Universal/HSpace.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | @available(*, deprecated, renamed: "UHSpace") 8 | public func HSpace(_ width: CGFloat) -> UView { UHSpace(width) } 9 | @available(*, deprecated, renamed: "UHSpace") 10 | public func HSpace(_ width: State) -> UView { UHSpace(width) } 11 | 12 | public func UHSpace(_ width: CGFloat) -> UView { UView().width(width) } 13 | public func UHSpace(_ width: State) -> UView { UView().width(width) } 14 | -------------------------------------------------------------------------------- /Classes/Views/Universal/HStack.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | open class UHStack: _StackView { 8 | public init (@BodyBuilder block: BodyBuilder.SingleView) { 9 | super.init(frame: .zero) 10 | #if os(macOS) 11 | orientation = .horizontal 12 | #else 13 | axis = .horizontal 14 | #endif 15 | add(item: block()) 16 | } 17 | 18 | public override init () { 19 | super.init(frame: .zero) 20 | #if os(macOS) 21 | orientation = .horizontal 22 | #else 23 | axis = .horizontal 24 | #endif 25 | } 26 | 27 | required public init(coder: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | 31 | public func subviews(@BodyBuilder block: BodyBuilder.SingleView) -> Self { 32 | add(item: block()) 33 | return self 34 | } 35 | 36 | public static func subviews(@BodyBuilder block: BodyBuilder.SingleView) -> UHStack { 37 | .init(block: block) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Classes/Views/Universal/Space.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | @available(*, deprecated, renamed: "USpace") 8 | public func Space() -> UView { USpace() } 9 | 10 | public func USpace() -> UView { UView() } 11 | -------------------------------------------------------------------------------- /Classes/Views/Universal/VSpace.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | @available(*, deprecated, renamed: "UVSpace") 8 | public func VSpace(_ height: CGFloat) -> UView { UVSpace(height) } 9 | @available(*, deprecated, renamed: "UVSpace") 10 | public func VSpace(_ height: State) -> UView { UVSpace(height) } 11 | 12 | public func UVSpace(_ height: CGFloat) -> UView { UView().height(height) } 13 | public func UVSpace(_ height: State) -> UView { UView().height(height) } 14 | -------------------------------------------------------------------------------- /Classes/Views/Universal/VStack.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import AppKit 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | open class UVStack: _StackView { 8 | public init (@BodyBuilder block: BodyBuilder.SingleView) { 9 | super.init(frame: .zero) 10 | #if os(macOS) 11 | orientation = .vertical 12 | #else 13 | axis = .vertical 14 | #endif 15 | add(item: block()) 16 | } 17 | 18 | public override init () { 19 | super.init(frame: .zero) 20 | #if os(macOS) 21 | orientation = .vertical 22 | #else 23 | axis = .vertical 24 | #endif 25 | } 26 | 27 | required public init(coder: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | 31 | public func subviews(@BodyBuilder block: BodyBuilder.SingleView) -> Self { 32 | add(item: block()) 33 | return self 34 | } 35 | 36 | public static func subviews(@BodyBuilder block: BodyBuilder.SingleView) -> UVStack { 37 | .init(block: block) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 MihaelIsaev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "UIKitPlus", 8 | platforms: [ 9 | .macOS(.v10_14), .iOS(.v9), .tvOS(.v13), 10 | ], 11 | products: [ 12 | // 🏰 Declarative UIKit wrapper inspired by SwiftUI 13 | .library(name: "UIKitPlus", targets: ["UIKitPlus"]), 14 | ], 15 | dependencies: [], 16 | targets: [ 17 | .target(name: "UIKitPlus", dependencies: [], path: "Classes"), 18 | // .testTarget(name: "UIKitPlusTests", dependencies: ["UIKitPlus"]), 19 | ] 20 | ) 21 | -------------------------------------------------------------------------------- /Templates/Project Templates/Application/UIKitPlus App.xctemplate/App.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import UIKitPlus 4 | 5 | class App: BaseApp { 6 | @AppBuilder override var body: AppBuilderContent { 7 | Lifecycle.didFinishLaunching { 8 | self.$pushNotificationToken.listen { token in 9 | // handle your push token here 10 | print("push token: \(token ?? "n/a")") 11 | } 12 | // request push token authorization like this 13 | App.requestPushNotificationsAuthorization(.alert, .sound, .badge) 14 | }.willResignActive { 15 | 16 | }.willEnterForeground { 17 | 18 | } 19 | MainScene { 20 | // Session.isAuthorized ? .splash : .login 21 | .splash 22 | }.splashScreen { 23 | SplashViewController() 24 | }.loginScreen { 25 | NavigationController(WelcomeViewController()).style(.transparent).hideNavigationBar() 26 | }.mainScreen { 27 | NavigationController(MainViewController()).style(.transparent).hideNavigationBar() 28 | } 29 | // Shortcuts { 30 | // Shortcut("A").title("Test 1").icon(type: .audio) 31 | // Shortcut("B").title("Test 2").subTitle("Hello world").icon(type: .bookmark) 32 | // } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Templates/Project Templates/Application/UIKitPlus App.xctemplate/Extensions/Buttons.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import UIKitPlus 4 | 5 | extension UButton { 6 | static var back: UButton { 7 | UButton()//.image("back") 8 | } 9 | static var gray: UButton { 10 | UButton() 11 | .circle() 12 | .background(.lightGray / .darkGray) 13 | .color(.black / .white) 14 | .height(54) 15 | .font(.helveticaNeueMedium, 16) 16 | } 17 | static var github: UButton { 18 | UButton() 19 | .circle() 20 | .background(.blackHole / .white) 21 | .color(.white / .black) 22 | .height(54) 23 | .font(.helveticaNeueMedium, 16) 24 | } 25 | } 26 | 27 | #if canImport(SwiftUI) && DEBUG 28 | import SwiftUI 29 | @available(iOS 13.0, *) 30 | struct Buttons_Preview: PreviewProvider, DeclarativePreviewGroup { 31 | static var previewGroup: PreviewGroup { 32 | PreviewGroup { 33 | Preview { 34 | UButton.gray.title("Gray Button").edgesToSuperview(h: 8).centerYInSuperview() 35 | } 36 | .title("gray") 37 | .colorScheme(.dark) 38 | .layout(.fixed(width: 375, height: 60)) 39 | Preview { 40 | UButton.github.title("Github").edgesToSuperview(h: 8).centerYInSuperview() 41 | } 42 | .title("github") 43 | .colorScheme(.light) 44 | .layout(.fixed(width: 375, height: 60)) 45 | } 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /Templates/Project Templates/Application/UIKitPlus App.xctemplate/Extensions/Colors.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import UIKitPlus 4 | 5 | extension UIColor { 6 | /// dynamic color (will be different in `dark` and `light` modes) 7 | static var dynamicBlack = .black / .white /// `.white` will be used for `darkMode` 8 | static var dynamicWhite = .white / .black /// `.black` will be used for `darkMode` 9 | 10 | /// `hex` color 11 | static var blackHole = 0x171A1D.color 12 | 13 | /// classic `UIColor` 14 | static var myColor = UIColor(red: 35, green: 127, blue: 201, alpha: 1) 15 | } 16 | 17 | #if canImport(SwiftUI) && DEBUG 18 | import SwiftUI 19 | @available(iOS 13.0, *) 20 | struct Colors_Preview: PreviewProvider, DeclarativePreviewGroup { 21 | static var previewGroup: PreviewGroup { 22 | PreviewGroup { 23 | Preview { 24 | UView().edgesToSuperview().background(.dynamicBlack) 25 | } 26 | .title("Dynamic Black") 27 | .colorScheme(.light) 28 | .layout(.fixed(width: 200, height: 50)) 29 | Preview { 30 | UView().edgesToSuperview().background(.dynamicWhite) 31 | } 32 | .title("Dynamic White") 33 | .colorScheme(.light) 34 | .layout(.fixed(width: 200, height: 50)) 35 | Preview { 36 | UView().edgesToSuperview().background(.red) 37 | } 38 | .title("Red") 39 | .colorScheme(.light) 40 | .layout(.fixed(width: 200, height: 50)) 41 | Preview { 42 | UView().edgesToSuperview().background(.green) 43 | } 44 | .title("Green") 45 | .colorScheme(.light) 46 | .layout(.fixed(width: 200, height: 50)) 47 | Preview { 48 | UView().edgesToSuperview().background(.blue) 49 | } 50 | .title("Blue") 51 | .colorScheme(.light) 52 | .layout(.fixed(width: 200, height: 50)) 53 | } 54 | } 55 | } 56 | #endif 57 | -------------------------------------------------------------------------------- /Templates/Project Templates/Application/UIKitPlus App.xctemplate/Extensions/Fonts.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import UIKitPlus 4 | 5 | /// Declare your custom fonts this way 6 | /// 7 | /// TIP: to get the list of all available font names on device 8 | /// run `UIFont.printAll()` in `AppDelegate.didFinishLaunchingWithOptions` 9 | /// 10 | /// if you'd like to add custom fonts 11 | /// 1) add them to the project files e.g. `Fonts` folder 12 | /// 2) add them to the `Info.plist` 13 | /// 3) print them all in `AppDelegate` 14 | /// 4) add them in this file 15 | /// 16 | 17 | extension FontIdentifier { 18 | static var helveticaNeueRegular = FontIdentifier("HelveticaNeue") 19 | static var helveticaNeueMedium = FontIdentifier("HelveticaNeue-Medium") 20 | static var helveticaNeueBold = FontIdentifier("HelveticaNeue-Bold") 21 | } 22 | 23 | #if canImport(SwiftUI) && DEBUG 24 | import SwiftUI 25 | @available(iOS 13.0, *) 26 | struct Fonts_Preview: PreviewProvider, DeclarativePreviewGroup { 27 | static var previewGroup: PreviewGroup { 28 | PreviewGroup { 29 | Preview { 30 | UText("This is helvetica neue regular font") 31 | .font(.helveticaNeueRegular, 16) 32 | .centerInSuperview() 33 | .color(.black / .white) 34 | } 35 | .colorScheme(.light) 36 | .layout(.fixed(width: 375, height: 50)) 37 | Preview { 38 | UText("This is helvetica neue medium font") 39 | .font(.helveticaNeueMedium, 16) 40 | .centerInSuperview() 41 | .color(.black / .white) 42 | } 43 | .colorScheme(.light) 44 | .layout(.fixed(width: 375, height: 50)) 45 | Preview { 46 | UText("This is helvetica neue bold font") 47 | .font(.helveticaNeueBold, 16) 48 | .centerInSuperview() 49 | .color(.black / .white) 50 | } 51 | .colorScheme(.light) 52 | .layout(.fixed(width: 375, height: 50)) 53 | } 54 | } 55 | } 56 | #endif 57 | -------------------------------------------------------------------------------- /Templates/Project Templates/Application/UIKitPlus App.xctemplate/Extensions/Images.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import UIKitPlus 4 | 5 | /// this way to declare `UIImage` and use it like `UImage(.myCustomImage)` 6 | extension UIImage { 7 | // static var iconBack: UIImage? { UIImage(named: "iconBack") } 8 | // static var iconPaperclip: UIImage? { UIImage(named: "iconPaperclip") } 9 | // static var iconSmile: UIImage? { UIImage(named: "iconSmile") } 10 | } 11 | 12 | /// this way to declare `Image` and use it like `Image.myCustomImage` 13 | extension UImage { 14 | // static var ___: UImage { UImage("___").mode(.scaleAspectFit) } 15 | } 16 | -------------------------------------------------------------------------------- /Templates/Project Templates/Application/UIKitPlus App.xctemplate/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Templates/Project Templates/Application/UIKitPlus App.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihaelIsaev/UIKitPlus/6932b74068e7f6ee030fb74fc9a51d4673a5c4c0/Templates/Project Templates/Application/UIKitPlus App.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /Templates/Project Templates/Application/UIKitPlus App.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MihaelIsaev/UIKitPlus/6932b74068e7f6ee030fb74fc9a51d4673a5c4c0/Templates/Project Templates/Application/UIKitPlus App.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /Templates/Project Templates/Application/UIKitPlus App.xctemplate/ViewControllers/MainViewController.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import UIKitPlus 4 | 5 | final class MainViewController: ViewController { 6 | override func buildUI() { 7 | super.buildUI() 8 | self.view.backgroundColor = .white / .black 9 | body { 10 | UVStack { 11 | UText("Main View Controller") 12 | .color(.black / .white) 13 | UVSpace(8) 14 | UButton("Logout") 15 | .background(.clear) 16 | .backgroundHighlighted(.lightGray / .darkGray) 17 | .color(.black / .white) 18 | .height(44) 19 | .border(1, .red) 20 | .circle() 21 | .onTapGesture { 22 | // Session.destroy() 23 | App.mainScene.switch(to: .logout, animation: .dismiss) 24 | } 25 | }.centerInSuperview() 26 | } 27 | } 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | } 33 | } 34 | 35 | #if canImport(SwiftUI) && DEBUG 36 | import SwiftUI 37 | @available(iOS 13.0, *) 38 | struct MainViewController_Preview: PreviewProvider, DeclarativePreview { 39 | static var preview: Preview { 40 | Preview { 41 | MainViewController() 42 | } 43 | .colorScheme(.dark) 44 | .device(.iPhoneX) 45 | .language(.en) 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /Templates/Project Templates/Application/UIKitPlus App.xctemplate/ViewControllers/SplashViewController.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import UIKitPlus 4 | 5 | final class SplashViewController: ViewController { 6 | override func buildUI() { 7 | super.buildUI() 8 | self.view.backgroundColor = .white / .black 9 | body { 10 | UVStack { 11 | UText("Splash View Controller") 12 | .compressionResistance(x: .defaultHigh) 13 | .color(.black / .white) 14 | UVSpace(8) 15 | ActivityIndicator().color(.dynamicBlack).animate() 16 | } 17 | .alignment(.center) 18 | .centerInSuperview() 19 | } 20 | } 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { 25 | App.mainScene.switch(to: .main, animation: .fade) 26 | } 27 | } 28 | } 29 | 30 | #if canImport(SwiftUI) && DEBUG 31 | import SwiftUI 32 | @available(iOS 13.0, *) 33 | struct SplashViewController_Preview: PreviewProvider, DeclarativePreview { 34 | static var preview: Preview { 35 | Preview { 36 | SplashViewController() 37 | } 38 | .colorScheme(.dark) 39 | .device(.iPhoneX) 40 | .language(.en) 41 | } 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /Templates/Project Templates/Application/UIKitPlus App.xctemplate/ViewControllers/WelcomeViewController.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import UIKitPlus 4 | 5 | final class WelcomeViewController: ViewController { 6 | // override var statusBarStyle: StatusBarStyle { .dark } 7 | 8 | override func buildUI() { 9 | super.buildUI() 10 | self.view.backgroundColor = .white / .black 11 | body { 12 | UVStack { 13 | UText("Welcome View Controller") 14 | .color(.black / .white) 15 | UVSpace(8) 16 | UButton("Enter") 17 | .background(.clear) 18 | .backgroundHighlighted(.lightGray / .darkGray) 19 | .color(.black / .white) 20 | .height(44) 21 | .border(1, .green) 22 | .circle() 23 | .onTapGesture { 24 | App.mainScene.switch(to: .splash, animation: .fade) 25 | } 26 | }.centerInSuperview() 27 | } 28 | } 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | } 34 | } 35 | 36 | #if canImport(SwiftUI) && DEBUG 37 | import SwiftUI 38 | @available(iOS 13.0, *) 39 | struct WelcomeViewController_Preview: PreviewProvider, DeclarativePreview { 40 | static var preview: Preview { 41 | Preview { 42 | WelcomeViewController() 43 | } 44 | .colorScheme(.dark) 45 | .device(.iPhoneX) 46 | .language(.en) 47 | } 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /UIKit-Plus.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint UIKit-Plus.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'UIKit-Plus' 11 | s.module_name = 'UIKitPlus' 12 | s.version = '2.2.0' 13 | s.summary = '🏰 Declarative UIKit wrapper inspired by SwiftUI' 14 | 15 | s.swift_version = '5.5' 16 | 17 | # This description is used to generate tags and improve search results. 18 | # * Think: What does it do? Why did you write it? What is the focus? 19 | # * Try to keep it short, snappy and to the point. 20 | # * Write the description between the DESC delimiters below. 21 | # * Finally, don't worry about the indent, CocoaPods strips it! 22 | 23 | s.description = 'Easy to use declarative UIKit wrapper inspired by SwiftUI' 24 | 25 | s.homepage = 'https://github.com/MihaelIsaev/UIKitPlus' 26 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 27 | s.license = { :type => 'MIT', :file => 'LICENSE' } 28 | s.author = { 'MihaelIsaev' => 'isaev.mihael@gmail.com' } 29 | s.source = { :git => 'https://github.com/MihaelIsaev/UIKitPlus.git', :tag => s.version.to_s } 30 | s.social_media_url = 'https://twitter.com/MihaelIsaev' 31 | 32 | s.ios.deployment_target = '9.0' 33 | s.macos.deployment_target = '10.15' 34 | 35 | s.source_files = 'Classes/**/*' 36 | 37 | # s.resource_bundles = { 38 | # 'UIKitPlus' => ['UIKitPlus/Assets/*.png'] 39 | # } 40 | 41 | # s.public_header_files = 'Pod/Classes/**/*.h' 42 | # s.frameworks = 'UIKit' 43 | 44 | # SwiftUI fix for "-weak_framework SwiftUI" } 46 | end 47 | --------------------------------------------------------------------------------