Compare commits
32 Commits
1.1.5
..
ui_examples
| Author | SHA1 | Date | |
|---|---|---|---|
| 4738558207 | |||
| ba36c06666 | |||
| 41cc7c725a | |||
| 409f526cf6 | |||
| b2b30b147f | |||
| a6b09007b2 | |||
| 7b001c292f | |||
| 5498067ffd | |||
| dce0be5a86 | |||
| 0e952505a9 | |||
| 43fca160be | |||
| 3453526d51 | |||
| 237bd57aca | |||
| 5ae4a97450 | |||
| e9a9f8add1 | |||
| 615b955965 | |||
| 943963d8c7 | |||
| f0c7d0abb9 | |||
| 46bb7d49f7 | |||
| 50d52c70a4 | |||
| 85d52cd837 | |||
| 18c90f0c63 | |||
| 15bfb95e8a | |||
| e87a0a40b6 | |||
| 341ed54032 | |||
| bdf75322b8 | |||
| 0534cba5df | |||
| 831b2f144f | |||
| 8bff1e5c24 | |||
| 4f1ca8157a | |||
| 8d6589004b | |||
| 3993d4d003 |
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ContainerControllerSwift'
|
||||
s.version = '1.1.3'
|
||||
s.version = '1.1.7'
|
||||
s.summary = 'This is a swipe-panel from application: https://www.apple.com/ios/maps/'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
@@ -33,8 +33,7 @@ TODO: Add long description of the pod here.
|
||||
# s.ios.deployment_target = '13.0'
|
||||
s.platform = :ios, "13.0"
|
||||
|
||||
s.source_files = '**/ContainerControllerSwift/**/*.{swift}'
|
||||
s.exclude_files = '**/ContainerControllerSwift/**/*.plist'
|
||||
s.source_files = 'Sources/**/*.{swift}'
|
||||
|
||||
s.framework = "UIKit"
|
||||
# s.ios.framework = 'UIKit'
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// irbis-Bridging-Header.h
|
||||
// irbis
|
||||
//
|
||||
// Created by Aleksandr Lobyncev on 07.04.2021.
|
||||
// Copyright © 2021 ИТСофт. All rights reserved.
|
||||
//
|
||||
|
||||
//#import "AppDelegate.h"
|
||||
|
||||
#import "NSObject+Extension.h"
|
||||
|
||||
//#import "Coffee.h"
|
||||
|
||||
//#import <Foundation/Foundation.h>
|
||||
//#import <Firebase/Firebase.h>
|
||||
//#import <GoogleMaps/GoogleMaps.h>
|
||||
//#import "Constants.h"
|
||||
//#import "ChangeAppIcon.h"
|
||||
//#import "CoffeeFilterType.h"
|
||||
//#import "CoffeeInstagram.h"
|
||||
//#import "Alert.h"
|
||||
////#import "AppDelegate.h"
|
||||
//
|
||||
////MARK: - CoffeeOld
|
||||
//
|
||||
//#import "UIView+Frame.h"
|
||||
//#import "Utils.h"
|
||||
//#import "NSString+JSONString.h"
|
||||
//#import "NSDate+Utils.h"
|
||||
//
|
||||
//#import "KGLocalizationManager.h"
|
||||
//#import "KGNotificationsManager.h"
|
||||
//
|
||||
//#import "CustomSegmentView.h"
|
||||
//
|
||||
//#import <MessageUI/MessageUI.h>
|
||||
//#import <MessageUI/MFMailComposeViewController.h>
|
||||
//
|
||||
//#import "Reachability.h"
|
||||
//#import "ReachabilityAddons.h"
|
||||
//
|
||||
////MARK: - CoffeeNew
|
||||
//
|
||||
//#import "AddonsNew.h"
|
||||
//
|
||||
////MARK: - Xib / Designable Views
|
||||
//
|
||||
//#import "DesignView.h"
|
||||
//#import "DesignButton.h"
|
||||
//#import "DesignSegmentBar.h"
|
||||
//
|
||||
////MARK: - Table Block
|
||||
//
|
||||
//#import "TableItem.h"
|
||||
//#import "BlockTableView.h"
|
||||
//
|
||||
////MARK: - Tag Styled
|
||||
//
|
||||
//#import "CollectionItem.h"
|
||||
//#import "TagsStyledView.h"
|
||||
//
|
||||
////MARK: - Xib / Storyboard loadNib
|
||||
//
|
||||
//#import "XibView.h"
|
||||
//#import "XibCell.h"
|
||||
//#import "XibCollectionCell.h"
|
||||
//#import "StoryboardController.h"
|
||||
//
|
||||
////MARK: - Container View
|
||||
//
|
||||
//#import "ContainerTypes.h"
|
||||
//#import "ContainerView.h" // View
|
||||
//#import "ContainerData.h" // Data
|
||||
//#import "ContainerViewController.h" // Screens
|
||||
//#import "ContainerMapViewController.h"
|
||||
//
|
||||
////MARK: - Coffee Data
|
||||
//
|
||||
//#import "CoffeeUtils.h" // utils
|
||||
//
|
||||
///// models
|
||||
//#import "CoffeeNew.h"
|
||||
//@class Coffee;
|
||||
//#import "Coffee.h"
|
||||
//#import "CoffeeInfoIP.h"
|
||||
//#import "CoffeeAddress.h"
|
||||
//#import "CoffeeCountry.h"
|
||||
//#import "CoffeeEvent.h"
|
||||
//#import "CoffeeMarketStore.h"
|
||||
//#import "CoffeeMarket.h"
|
||||
//#import "CoffeeAdmin.h"
|
||||
//
|
||||
///// container data
|
||||
////#import "CoffeeOldListContainerData.h"
|
||||
////#import "CoffeeOldDetailsContainerData.h"
|
||||
////#import "CoffeeOld2ListContainerData.h"
|
||||
////#import "CoffeeOld2DetailsContainerData.h"
|
||||
//#import "CoffeeListContainerData.h"
|
||||
//#import "CoffeeDetailsContainerData.h"
|
||||
//
|
||||
///// managers
|
||||
//#import "ConfigManager.h"
|
||||
//#import "DownloadManager.h"
|
||||
//#import "DownloadInfoSite.h"
|
||||
//#import "CoffeeMapGetJSON.h"
|
||||
//#import "CoffeeDataManager.h"
|
||||
//#import "CoffeeMapManager.h"
|
||||
//
|
||||
//#define DATA_MGR [CoffeeDataManager managers]
|
||||
//#define MAP_MGR [CoffeeMapManager managers]
|
||||
//
|
||||
////MARK: - UI
|
||||
//
|
||||
///// cells
|
||||
//#import "MenuCells.h"
|
||||
//#import "TextTitleCell.h" // text
|
||||
//#import "CoffeeSegmentCell.h"
|
||||
//#import "CoffeeMenuAboutCell.h"
|
||||
//#import "CoffeeMenuTextCell.h"
|
||||
//#import "CoffeeCell.h" // coffee
|
||||
////#import "CoffeeDetailsCell.h"
|
||||
//#import "CoffeeDetailsTitleCell.h"
|
||||
//#import "CoffeeDetailsRouteCell.h"
|
||||
//#import "CoffeeDetailsContentCell.h"
|
||||
//#import "CoffeeDetailsInstaCell.h"
|
||||
//#import "CoffeeDetailsAddressCell.h"
|
||||
//#import "CoffeeDetailsInfoNewCell.h"
|
||||
//#import "CoffeeDetailsInfoCell.h"
|
||||
//#import "CoffeeListEventCell.h" // events
|
||||
//#import "CoffeeDetailsEventTitleCell.h"
|
||||
//#import "CoffeeDetailsEventPhotoCell.h"
|
||||
//#import "CoffeeDetailsEventInfoCell.h"
|
||||
//#import "CoffeeMarketTitleCell.h" // market
|
||||
//#import "CoffeeMarketNameCell.h"
|
||||
//#import "CoffeeMarketSectionCell.h"
|
||||
//#import "CoffeeMarketSubtitleCell.h"
|
||||
//#import "CoffeeMarketInfoCell.h"
|
||||
//#import "CoffeeMenuSocItemsCell.h"
|
||||
//#import "CoffeeMenuSectionCell.h"
|
||||
//#import "CoffeeListEmptyCell.h" // empty
|
||||
//#import "GTRideMapCell.h"
|
||||
//#import "CoffeeMapPlaceEventCell.h"
|
||||
//
|
||||
///// cells collection
|
||||
//#import "CoffeeMenuSocCell.h"
|
||||
//#import "CoffeeTagCell.h"
|
||||
//#import "CoffeeMarketCell.h"
|
||||
//#import "CoffeeMarketEmptyTagCell.h"
|
||||
//#import "CoffeeMarketSectionTagCell.h"
|
||||
//#import "CoffeeMarketOneStoreTagCell.h"
|
||||
//#import "CoffeeMarketStoresTagCell.h"
|
||||
//
|
||||
///// view
|
||||
//#import "CoffeeHeaderView.h" // coffee
|
||||
//#import "CoffeeHeaderGripView.h"
|
||||
//#import "CoffeeHeaderGripTitleView.h"
|
||||
//#import "CoffeeHeaderSearchBarView.h"
|
||||
//#import "CoffeeDetailsHeaderView.h"
|
||||
//#import "CoffeeDetailsTitleHeaderView.h"
|
||||
//#import "CoffeeNavBarView.h"
|
||||
////#import "CoffeeTabBarOld2View.h"
|
||||
//#import "CoffeeTabBarView.h"
|
||||
//#import "CoffeeListEmptyView.h"
|
||||
//
|
||||
///// screen
|
||||
//#import "WorkspaceController.h"
|
||||
//#import "CoffeeViewController.h" // coffee
|
||||
//#import "FLAnimatedImage.h"
|
||||
//#import "InstagramManager.h"
|
||||
////#import "CoffeeOld2ViewController.h"
|
||||
////#import "CoffeeOldViewController.h"
|
||||
//#import "CoffeeMenuViewController.h"
|
||||
//
|
||||
//#import "CoffeeEventsViewController.h"
|
||||
//#import "CoffeeEventsDetailsViewController.h"
|
||||
//#import "CoffeeMarketsViewController.h"
|
||||
//#import "CoffeeMarketsDetailsViewController.h"
|
||||
//#import "DbCoffeeImagesViewController.h"
|
||||
//#import "CoffeeNewsFeedViewController.h"
|
||||
//#import "ContainerMapRedViewController.h"
|
||||
//#import "CoffeeAdminViewController.h"
|
||||
//#import "CoffeePushViewController.h"
|
||||
//#import "SendPush.h"
|
||||
//
|
||||
//
|
||||
//#import "SafariViewController.h"
|
||||
//#import "WebViewController.h"
|
||||
File diff suppressed because it is too large
Load Diff
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:ContainerControllerSwift.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1540"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "ContainerController.app"
|
||||
BlueprintName = "ContainerControllerSwift_Example"
|
||||
ReferencedContainer = "container:ContainerControllerSwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACE41AFB9204008FA782"
|
||||
BuildableName = "ContainerControllerSwift_Tests.xctest"
|
||||
BlueprintName = "ContainerControllerSwift_Tests"
|
||||
ReferencedContainer = "container:ContainerControllerSwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "ContainerController.app"
|
||||
BlueprintName = "ContainerControllerSwift_Example"
|
||||
ReferencedContainer = "container:ContainerControllerSwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACE41AFB9204008FA782"
|
||||
BuildableName = "ContainerControllerSwift_Tests.xctest"
|
||||
BlueprintName = "ContainerControllerSwift_Tests"
|
||||
ReferencedContainer = "container:ContainerControllerSwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "ContainerController.app"
|
||||
BlueprintName = "ContainerControllerSwift_Example"
|
||||
ReferencedContainer = "container:ContainerControllerSwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "IDEPreferLogStreaming"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
||||
BuildableName = "ContainerController.app"
|
||||
BlueprintName = "ContainerControllerSwift_Example"
|
||||
ReferencedContainer = "container:ContainerControllerSwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:..">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:/Users/rustam/.cocoapods/repos/ContainerControllerSwift/Example/ContainerControllerSwift.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// ContainerControllerSwift
|
||||
//
|
||||
// Created by rustamburger@gmail.com on 06/09/2020.
|
||||
// Copyright (c) 2020 rustamburger@gmail.com. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_0" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="ContainerController Swift" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="169" width="280" height="43"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="screenLandscape5.png" translatesAutoresizingMaskIntoConstraints="NO" id="c2h-J4-UI4">
|
||||
<rect key="frame" x="15" y="60" width="290" height="508"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstItem="c2h-J4-UI4" firstAttribute="height" secondItem="iN0-l3-epB" secondAttribute="height" multiplier="0.894089" id="6Pl-Y0-TbW"/>
|
||||
<constraint firstItem="c2h-J4-UI4" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="15" id="8cY-so-aq9"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="trailing" secondItem="c2h-J4-UI4" secondAttribute="trailing" constant="15" id="Tlx-R0-BaC"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
<constraint firstAttribute="bottom" secondItem="c2h-J4-UI4" secondAttribute="bottom" id="nuV-b0-dKE"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<point key="canvasLocation" x="544.79999999999995" y="453.69458128078821"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="screenLandscape5.png" width="685.5" height="302.5"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="cIu-62-xtU">
|
||||
<device id="retina5_9" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="ContainerController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="718"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" contentViewInsetsToSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UYm-tY-WGa" customClass="TableAdapterView" customModule="ContainerControllerSwift">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="718"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="W4d-9e-HAg"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="UYm-tY-WGa" secondAttribute="bottom" id="kha-w5-3Yg"/>
|
||||
<constraint firstItem="UYm-tY-WGa" firstAttribute="top" secondItem="8bC-Xf-vdC" secondAttribute="top" id="qmS-hQ-rSa"/>
|
||||
<constraint firstAttribute="trailing" secondItem="UYm-tY-WGa" secondAttribute="trailing" id="vQK-T0-eQf"/>
|
||||
<constraint firstItem="UYm-tY-WGa" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="wXp-OO-MUn"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="x48-lm-dG7"/>
|
||||
<connections>
|
||||
<outlet property="tableView" destination="UYm-tY-WGa" id="djd-oP-Z2M"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="132" y="136.69950738916256"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="jWl-Kd-Y9J">
|
||||
<objects>
|
||||
<navigationController id="cIu-62-xtU" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="y83-TR-htY">
|
||||
<rect key="frame" x="0.0" y="50" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="barTintColor" systemColor="secondaryLabelColor"/>
|
||||
<navigationBarAppearance key="compactAppearance"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="Lpq-ZC-BiY"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bJL-5a-VyA" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-855" y="137"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="secondaryLabelColor">
|
||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,112 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIColor {
|
||||
var redValue: CGFloat{ return CIColor(color: self).red }
|
||||
var greenValue: CGFloat{ return CIColor(color: self).green }
|
||||
var blueValue: CGFloat{ return CIColor(color: self).blue }
|
||||
var alphaValue: CGFloat{ return CIColor(color: self).alpha }
|
||||
|
||||
|
||||
|
||||
static let playFullPanelOld = UIColor(named: "PlayFullPanelOld")!
|
||||
static let playFullBackground = UIColor(named: "PlayFullBackground")!
|
||||
static let playFullBackgroundOld = UIColor(named: "PlayFullBackgroundOld")!
|
||||
static let playlistColor = UIColor(named: "PlaylistColor")!
|
||||
static let playMusicColor = UIColor(named: "PlayMusicColor")! // red
|
||||
static let sportColor = UIColor(named: "SportColor")!
|
||||
}
|
||||
|
||||
class Colors {
|
||||
|
||||
class func hex(_ hex: Int) -> UIColor {
|
||||
let mask = 0xFF
|
||||
let r = CGFloat((hex >> 16) & mask) / 255
|
||||
let g = CGFloat((hex >> 8) & mask) / 255
|
||||
let b = CGFloat((hex) & mask) / 255
|
||||
return UIColor(red: r, green: g, blue: b, alpha: 1)
|
||||
}
|
||||
|
||||
|
||||
class func hexStr(_ hex: String) -> UIColor {
|
||||
let input = hex.replacingOccurrences(of: "#", with: "").uppercased()
|
||||
var a: CGFloat = 1.0, r: CGFloat = 0.0, b: CGFloat = 0.0, g: CGFloat = 0.0
|
||||
func colorComponent(from string: String, start: Int, length: Int) -> CGFloat {
|
||||
let substring = (string as NSString).substring(with: NSRange(location: start, length: length))
|
||||
let fullHex = length == 2 ? substring : "\(substring)\(substring)"
|
||||
var hexComponent: UInt64 = 0
|
||||
Scanner(string: fullHex).scanHexInt64(&hexComponent)
|
||||
return CGFloat(Double(hexComponent) / 255.0)
|
||||
}
|
||||
switch (input.count) {
|
||||
case 3 /* #RGB */:
|
||||
r = colorComponent(from: input, start: 0, length: 1)
|
||||
g = colorComponent(from: input, start: 1, length: 1)
|
||||
b = colorComponent(from: input, start: 2, length: 1)
|
||||
case 4 /* #ARGB */:
|
||||
a = colorComponent(from: input, start: 0, length: 1)
|
||||
r = colorComponent(from: input, start: 1, length: 1)
|
||||
g = colorComponent(from: input, start: 2, length: 1)
|
||||
b = colorComponent(from: input, start: 3, length: 1)
|
||||
case 6 /* #RRGGBB */:
|
||||
r = colorComponent(from: input, start: 0, length: 2)
|
||||
g = colorComponent(from: input, start: 2, length: 2)
|
||||
b = colorComponent(from: input, start: 4, length: 2)
|
||||
case 8 /* #AARRGGBB */:
|
||||
a = colorComponent(from: input, start: 0, length: 2)
|
||||
r = colorComponent(from: input, start: 2, length: 2)
|
||||
g = colorComponent(from: input, start: 4, length: 2)
|
||||
b = colorComponent(from: input, start: 6, length: 2)
|
||||
default:
|
||||
break
|
||||
}
|
||||
return UIColor(red: r, green: g, blue: b, alpha: a)
|
||||
}
|
||||
|
||||
class func rgba( _ red: CGFloat, _ green: CGFloat, _ blue: CGFloat, _ alpha: CGFloat) -> UIColor {
|
||||
return UIColor(red: red / 255, green: green / 255, blue: blue / 255, alpha: alpha)
|
||||
}
|
||||
|
||||
class func rgb( _ red: CGFloat, _ green: CGFloat, _ blue: CGFloat) -> UIColor {
|
||||
return rgba(red, green, blue, 1)
|
||||
}
|
||||
|
||||
class func grayLevel(_ gray: CGFloat) -> UIColor {
|
||||
return rgb(gray * 255, gray * 255, gray * 255)
|
||||
}
|
||||
|
||||
class func blackAlpha(_ alpha: CGFloat) -> UIColor {
|
||||
return rgba(0, 0, 0, alpha)
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
static public let lightGray = blackAlpha(0.1)
|
||||
static public let slightlyDark = blackAlpha(0.3)
|
||||
static public let halfBlack = blackAlpha(0.5)
|
||||
|
||||
static public let black = grayLevel(0) // 0 %
|
||||
static public let gray = grayLevel(127) // 49 %
|
||||
static public let lightInactiveGray = grayLevel(178) // 69 %
|
||||
static public let silver = grayLevel(229) // 89 %
|
||||
static public let inactiveGray = grayLevel(246) // 96 %
|
||||
static public let white = grayLevel(255) // 100 %
|
||||
|
||||
static public let transparentGray = rgba(225, 225, 225, 0.3)
|
||||
static public let lightOrange = rgba(255, 105, 0, 0.1)
|
||||
|
||||
static public let red = rgb(255, 59, 48)
|
||||
static public let blue = rgb(44, 174, 233)
|
||||
static public let yellow = rgb(254, 219, 6)
|
||||
static public let orange = rgb(255, 105, 0)
|
||||
static public let purple = rgb(128, 0, 128)
|
||||
static public let gold = rgb(226, 201, 127)
|
||||
static public let beige = rgb(245, 245, 220)
|
||||
static public let brand = rgb(255, 105, 0)
|
||||
static public let approveGreen = rgb(49, 183, 0)
|
||||
static public let darkBlue = rgb(74, 144, 226)
|
||||
static public let semidarkBlue = rgb(0, 107, 202)
|
||||
static public let darkGray = rgb(142, 142, 147)
|
||||
static public let lightYellow = rgb(254, 229, 6)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.129",
|
||||
"green" : "0.086",
|
||||
"red" : "0.078"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.129",
|
||||
"green" : "0.086",
|
||||
"red" : "0.078"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.137",
|
||||
"green" : "0.122",
|
||||
"red" : "0.118"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.137",
|
||||
"green" : "0.122",
|
||||
"red" : "0.118"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.157",
|
||||
"green" : "0.129",
|
||||
"red" : "0.125"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.157",
|
||||
"green" : "0.129",
|
||||
"red" : "0.125"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.365",
|
||||
"green" : "0.267",
|
||||
"red" : "0.922"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.365",
|
||||
"green" : "0.267",
|
||||
"red" : "0.922"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.937",
|
||||
"green" : "0.518",
|
||||
"red" : "0.310"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.937",
|
||||
"green" : "0.518",
|
||||
"red" : "0.310"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.000",
|
||||
"green" : "0.541",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.000",
|
||||
"green" : "0.541",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Pavel Puzyrev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Blurberry
|
||||
|
||||
Transparent blur using UIVisualEffectView without subclassing.
|
||||
|
||||

|
||||
|
||||
[[video demo]](https://vimeo.com/457206677)
|
||||
|
||||
[[appetize demo]](https://appetize.io/app/u2udhgwh0cgm12at0rmgjxtphr)
|
||||
|
||||
## Features
|
||||
- Based on UIVisualEffectView
|
||||
- Supports **iOS 14** and animation blocks
|
||||
- Super safe and super easy
|
||||
|
||||
## Usage
|
||||
|
||||
Just create a `UIVisualEffectView` in any way. Code, storyboards, XIBs, etc.
|
||||
|
||||
Customize it like example bellow via `blur` wrapper:
|
||||
```swift
|
||||
visualEffectView.blur.radius = 5.0
|
||||
visualEffectView.blur.tintColor = .clear
|
||||
```
|
||||
|
||||
Don't forget to set a `tintColor` value, otherwise it will be 30% white like `UIBlurEffect.Style.Light` by default.
|
||||
|
||||
## Installation
|
||||
|
||||
### Cocoapods
|
||||
|
||||
Add the Blurberry pod to your Podfile:
|
||||
```ruby
|
||||
platform :ios, '10.0'
|
||||
|
||||
use_frameworks!
|
||||
|
||||
target 'BlurryApp' do
|
||||
pod 'Blurberry'
|
||||
end
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [x] iOS 10, 11, 12 support
|
||||
- [ ] Other platform support (such as macOS)
|
||||
- [ ] Remove ObjC code or make it private
|
||||
- [ ] Add manager to check blur availability and other service info
|
||||
- [ ] Hide private API class names, method names, etc.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This framework uses private API, so just keep in mind it before submitting to the AppStore.
|
||||
|
||||
## Requirements
|
||||
|
||||
- iOS 10.0+
|
||||
- Swift 5
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Blurberry.h
|
||||
// Blurberry
|
||||
//
|
||||
// Created by Pavel Puzyrev on 09.09.2020.
|
||||
//
|
||||
// Copyright (c) 2020 Pavel Puzyrev <cannedapp@yahoo.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for Blurberry.
|
||||
FOUNDATION_EXPORT double BlurberryVersionNumber;
|
||||
|
||||
//! Project version string for Blurberry.
|
||||
FOUNDATION_EXPORT const unsigned char BlurberryVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <Blurberry/PublicHeader.h>
|
||||
|
||||
#import <Blurberry/NSObject+Extension.h>
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// NSObject+Extension.h
|
||||
// Blurberry
|
||||
//
|
||||
// Created by Pavel Puzyrev on 07.09.2020.
|
||||
//
|
||||
// Copyright (c) 2020 Pavel Puzyrev <cannedapp@yahoo.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ContainerControllerSwift-Swift.h"
|
||||
|
||||
struct BlurWrapper;
|
||||
|
||||
@class UIColor;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSObject (Extension)
|
||||
|
||||
#pragma mark *** Values getting ***
|
||||
|
||||
- (nullable NSArray<NSObject *> *)arrayValueByGetterNamed:(NSString *)getterName NS_SWIFT_NAME(arrayValue(getter:));
|
||||
- (nullable NSDictionary<NSString *, id> *)dictionaryValueByGetterNamed:(NSString *)getterName NS_SWIFT_NAME(dictionaryValue(getter:));
|
||||
- (nullable NSString *)stringValueByGetterNamed:(NSString *)getterName NS_SWIFT_NAME(stringValue(getter:));
|
||||
- (nullable UIColor *)colorValueByGetterNamed:(NSString *)getterName NS_SWIFT_NAME(colorValue(getter:));
|
||||
|
||||
#pragma mark *** Values setting ***
|
||||
|
||||
- (void)setIVarValue:(nullable id)value getterNamed:(NSString *)getterName NS_SWIFT_NAME(setIVarValue(value:getter:));
|
||||
- (void)setValueUsingSetter:(nullable id)value getterNamed:(NSString *)getterName NS_SWIFT_NAME(setValueUsingSetter(value:getter:));
|
||||
- (void)setObjectInDictionary:(id)object
|
||||
forKey:(NSString *)key
|
||||
getterNamed:(NSString *)getterName NS_SWIFT_NAME(setObjectInDictionary(object:key:getter:));
|
||||
|
||||
- (nullable id)valueSafeForKey:(NSString *)key NS_SWIFT_NAME(valueSafe(key:));
|
||||
- (void)setValueSafe:(id)value forKey:(NSString *)key NS_SWIFT_NAME(setValueSafe(value:key:));
|
||||
|
||||
#pragma mark *** Methods calling ***
|
||||
|
||||
- (void)callMethodNamed:(NSString *)methodName NS_SWIFT_NAME(callMethod(named:));
|
||||
- (void)callMethodNamed:(NSString *)methodName withObject:(nullable id)argument NS_SWIFT_NAME(callMethod(named:with:));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// NSObject+Extension.m
|
||||
// Blurberry
|
||||
//
|
||||
// Created by Pavel Puzyrev on 07.09.2020.
|
||||
//
|
||||
// Copyright (c) 2020 Pavel Puzyrev <cannedapp@yahoo.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#import "NSObject+Extension.h"
|
||||
#import <objc/runtime.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
|
||||
//@class BlurWrapper;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation NSObject (Extension)
|
||||
|
||||
#pragma mark - Public Methods
|
||||
|
||||
#pragma mark *** Values getting ***
|
||||
|
||||
- (nullable NSArray<NSObject *> *)arrayValueByGetterNamed:(NSString *)getterName {
|
||||
return [self valueOfType:[NSArray class] getterNamed:getterName];
|
||||
}
|
||||
|
||||
- (nullable NSDictionary<NSString *, id> *)dictionaryValueByGetterNamed:(NSString *)getterName {
|
||||
return [self valueOfType:[NSDictionary class] getterNamed:getterName];
|
||||
}
|
||||
|
||||
- (nullable NSString *)stringValueByGetterNamed:(NSString *)getterName {
|
||||
return [self valueOfType:[NSString class] getterNamed:getterName];
|
||||
}
|
||||
|
||||
- (nullable UIColor *)colorValueByGetterNamed:(NSString *)getterName {
|
||||
return [self valueOfType:[UIColor class] getterNamed:getterName];
|
||||
}
|
||||
|
||||
#pragma mark *** Values setting ***
|
||||
|
||||
- (void)setIVarValue:(nullable id)value getterNamed:(NSString *)getterName {
|
||||
@try {
|
||||
SEL getterSelector = NSSelectorFromString(getterName);
|
||||
SEL setterSelector = NSSelectorFromString([self setterMethodName:getterName]);
|
||||
NSString *ivarName = [NSString stringWithFormat:@"_%@", getterName];
|
||||
if ([self respondsToSelector:getterSelector] && [self respondsToSelector:setterSelector]) {
|
||||
[self setValue:value forKey:ivarName];
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setValueUsingSetter:(nullable id)value getterNamed:(NSString *)getterName {
|
||||
[self callMethodNamed:[self setterMethodName:getterName] withObject:value];
|
||||
}
|
||||
|
||||
- (void)setObjectInDictionary:(id)object forKey:(NSString *)key getterNamed:(NSString *)getterName {
|
||||
NSMutableDictionary *dictionary = [[self dictionaryValueByGetterNamed:getterName] mutableCopy];
|
||||
if (!dictionary) {
|
||||
return;
|
||||
}
|
||||
|
||||
dictionary[key] = object;
|
||||
|
||||
[self callMethodNamed:[self setterMethodName:getterName] withObject:[dictionary copy]];
|
||||
}
|
||||
|
||||
- (nullable id)valueSafeForKey:(NSString *)key {
|
||||
@try {
|
||||
return [self valueForKey:key];
|
||||
} @catch (NSException *exception) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setValueSafe:(id)value forKey:(NSString *)key {
|
||||
@try {
|
||||
[self setValue:value forKey:key];
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark *** Methods calling ***
|
||||
|
||||
- (void)callMethodNamed:(NSString *)methodName {
|
||||
[self callMethodNamed:methodName withObject:nil];
|
||||
}
|
||||
|
||||
- (void)callMethodNamed:(NSString *)methodName withObject:(nullable id)argument {
|
||||
@try {
|
||||
SEL selector = NSSelectorFromString(methodName);
|
||||
if ([self respondsToSelector:selector]) {
|
||||
_Pragma("clang diagnostic push")
|
||||
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")
|
||||
if (argument) {
|
||||
[self performSelector:selector withObject:argument];
|
||||
} else {
|
||||
[self performSelector:selector];
|
||||
}
|
||||
_Pragma("clang diagnostic pop")
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Private Methods
|
||||
|
||||
- (nullable id)valueOfType:(Class)type getterNamed:(NSString *)getterName {
|
||||
id typedValue;
|
||||
@try {
|
||||
SEL selector = NSSelectorFromString(getterName);
|
||||
if ([self respondsToSelector:selector]) {
|
||||
_Pragma("clang diagnostic push")
|
||||
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")
|
||||
id value = [self performSelector:selector];
|
||||
if ([value isKindOfClass:type]) {
|
||||
typedValue = value;
|
||||
}_Pragma("clang diagnostic pop")
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
} @finally {
|
||||
return typedValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)setterMethodName:(NSString *)getterMethodName {
|
||||
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en-US"];
|
||||
NSString *firstChar = [getterMethodName substringToIndex:1];
|
||||
NSString *folded = [firstChar stringByFoldingWithOptions:NSDiacriticInsensitiveSearch locale:locale];
|
||||
NSString *pascalCasedResult = [folded.uppercaseString stringByAppendingString:[getterMethodName substringFromIndex:1]];
|
||||
|
||||
return [NSString stringWithFormat:@"set%@:", pascalCasedResult];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// Blurberry.swift
|
||||
// Blurberry
|
||||
//
|
||||
// Created by Pavel Puzyrev on 09.09.2020.
|
||||
//
|
||||
// Copyright (c) 2020 Pavel Puzyrev <cannedapp@yahoo.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct BlurWrapper<Base> {
|
||||
|
||||
public let base: Base
|
||||
|
||||
public init(_ base: Base) {
|
||||
self.base = base
|
||||
}
|
||||
}
|
||||
|
||||
public protocol BlurCompatible: AnyObject { }
|
||||
|
||||
public extension BlurCompatible {
|
||||
|
||||
var blur: BlurWrapper<Self> {
|
||||
get {
|
||||
BlurWrapper(self)
|
||||
}
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
||||
extension UIVisualEffectView: BlurCompatible { }
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// VisualEffectView+Internal.swift
|
||||
// Blurberry
|
||||
//
|
||||
// Created by Pavel Puzyrev on 07.09.2020.
|
||||
//
|
||||
// Copyright (c) 2020 Pavel Puzyrev <cannedapp@yahoo.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
internal extension BlurWrapper where Base: UIVisualEffectView {
|
||||
|
||||
// MARK: iOS 13, 14
|
||||
|
||||
var backdropView: UIView? {
|
||||
base.subviews.first {
|
||||
type(of: $0) == NSClassFromString("_UIVisualEffectBackdropView")
|
||||
}
|
||||
}
|
||||
|
||||
var overlayView: UIView? {
|
||||
base.subviews.first {
|
||||
type(of: $0) == NSClassFromString("_UIVisualEffectSubview")
|
||||
}
|
||||
}
|
||||
|
||||
var gaussianBlurFilter: NSObject? {
|
||||
backdropView?.arrayValue(getter: "filters")?.first {
|
||||
$0.stringValue(getter: "filterType") == "gaussianBlur"
|
||||
}
|
||||
}
|
||||
|
||||
var sourceOverEffect: NSObject? {
|
||||
overlayView?.arrayValue(getter: "viewEffects")?.first {
|
||||
$0.stringValue(getter: "filterType") == "sourceOver"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: iOS 10, 11, 12
|
||||
|
||||
var blurEffect: NSObject? {
|
||||
guard let effect = base.effect, type(of: effect) == NSClassFromString("_UICustomBlurEffect") else {
|
||||
return nil
|
||||
}
|
||||
return base.effect
|
||||
}
|
||||
|
||||
func updateBlurEffect(blurRadius: CGFloat, tintColor: UIColor?) {
|
||||
let effect = (NSClassFromString("_UICustomBlurEffect") as? UIBlurEffect.Type)?.init()
|
||||
effect?.setValueSafe(value: blurRadius, key: "blurRadius")
|
||||
effect?.setValueSafe(value: tintColor ?? UIColor(white: 1.0, alpha: 0.3), key: "colorTint")
|
||||
effect?.setValueSafe(value: 1.0, key: "scale")
|
||||
effect?.setValueSafe(value: 1.0, key: "colorTintAlpha")
|
||||
base.effect = effect
|
||||
}
|
||||
|
||||
// MARK: Changes
|
||||
|
||||
func prepareForChanges() {
|
||||
if #available(iOS 13, *) {
|
||||
base.effect = UIBlurEffect(style: .light)
|
||||
gaussianBlurFilter?.setIVarValue(value: 1.0, getter: "requestedScaleHint")
|
||||
}
|
||||
}
|
||||
|
||||
func applyChanges() {
|
||||
if #available(iOS 13, *) {
|
||||
backdropView?.callMethod(named: "applyRequestedFilterEffects")
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// VisualEffectView+Public.swift
|
||||
// Blurberry
|
||||
//
|
||||
// Created by Pavel Puzyrev on 09.09.2020.
|
||||
//
|
||||
// Copyright (c) 2020 Pavel Puzyrev <cannedapp@yahoo.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
|
||||
import UIKit
|
||||
import ContainerControllerSwift
|
||||
|
||||
|
||||
|
||||
public extension BlurWrapper where Base: UIVisualEffectView {
|
||||
|
||||
var radius: CGFloat {
|
||||
get {
|
||||
if #available(iOS 13, *) {
|
||||
return gaussianBlurFilter?.dictionaryValue(getter: "requestedValues")?["inputRadius"] as? CGFloat ?? 0.0
|
||||
} else {
|
||||
return blurEffect?.valueSafe(key: "blurRadius") as? CGFloat ?? 0.0
|
||||
}
|
||||
}
|
||||
set {
|
||||
prepareForChanges()
|
||||
|
||||
if #available(iOS 13, *) {
|
||||
gaussianBlurFilter?.setObjectInDictionary(object: newValue, key: "inputRadius", getter: "requestedValues")
|
||||
} else {
|
||||
updateBlurEffect(blurRadius: newValue, tintColor: tintColor)
|
||||
}
|
||||
|
||||
applyChanges()
|
||||
}
|
||||
}
|
||||
|
||||
var tintColor: UIColor? {
|
||||
get {
|
||||
if #available(iOS 13, *) {
|
||||
return sourceOverEffect?.colorValue(getter: "color")
|
||||
} else {
|
||||
return blurEffect?.colorValue(getter: "colorTint")
|
||||
}
|
||||
}
|
||||
set {
|
||||
prepareForChanges()
|
||||
|
||||
if #available(iOS 13, *) {
|
||||
sourceOverEffect?.setValueUsingSetter(value: newValue, getter: "color")
|
||||
sourceOverEffect?.callMethod(named: "applyRequestedEffectToView:", with: overlayView)
|
||||
} else {
|
||||
updateBlurEffect(blurRadius: radius, tintColor: newValue)
|
||||
}
|
||||
|
||||
applyChanges()
|
||||
}
|
||||
}
|
||||
}
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
//
|
||||
// UIButton+AllAnimations.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 20.08.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
import AVFoundation
|
||||
|
||||
// MARK: Animations
|
||||
|
||||
extension UIButton {
|
||||
|
||||
// MARK: - Alpha
|
||||
|
||||
func updateChangeAlpha(with views: [UIView], visible: Alpha, value: CGFloat, duration: Speed?) {
|
||||
|
||||
var alpha: CGFloat = 1.0
|
||||
if visible != .visible {
|
||||
alpha = value
|
||||
}
|
||||
|
||||
if let duration = duration {
|
||||
UIView.animate(with: duration) {
|
||||
for view in views {
|
||||
view.alpha = alpha
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for view in views {
|
||||
view.alpha = alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Layer Gray
|
||||
|
||||
func updateLayerGray(with views: [UIView], visible: Alpha, value: CGFloat, duration: Speed?) {
|
||||
|
||||
guard let mainView = views.last else { return }
|
||||
let shTag = 31
|
||||
let spechialColor = UIColor(red: 10.0 / 255.0, green: 13.0 / 255.0, blue: 38.0 / 255.0, alpha: value)
|
||||
|
||||
if visible == .visible {
|
||||
|
||||
if let view = mainView.viewWithTag(shTag) {
|
||||
view.alpha = 1
|
||||
|
||||
if let duration = duration {
|
||||
UIView.animate(
|
||||
with: duration,
|
||||
animations: {
|
||||
view.alpha = 0
|
||||
},
|
||||
completion: { fin in
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
view.alpha = 0
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if mainView.viewWithTag(shTag) == nil {
|
||||
|
||||
var radius = mainView.layer.cornerRadius
|
||||
if radius == 0 {
|
||||
for sv in mainView.subviews {
|
||||
if sv.layer.cornerRadius != 0 {
|
||||
radius = mainView.layer.cornerRadius
|
||||
}
|
||||
if let desFig = mainView as? DesignFigure {
|
||||
radius = desFig.cornerRadius
|
||||
if radius == -1 {
|
||||
let minSize = min(mainView.frame.width, mainView.bounds.height)
|
||||
radius = minSize / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let shadowView = UIView(frame: mainView.bounds)
|
||||
shadowView.tag = shTag
|
||||
shadowView.backgroundColor = spechialColor
|
||||
shadowView.layer.cornerRadius = radius
|
||||
mainView.addSubview(shadowView)
|
||||
shadowView.alpha = ((duration == nil) ? 1 : 0)
|
||||
if let duration = duration {
|
||||
if duration == .zero {
|
||||
shadowView.alpha = 1
|
||||
} else {
|
||||
UIView.animate(with: duration) {
|
||||
shadowView.alpha = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
// MARK: - Flash
|
||||
|
||||
func flash(duration: Speed = .ms200) {
|
||||
|
||||
let flash = CABasicAnimation(keyPath: "opacity")
|
||||
flash.duration = duration.rawValue
|
||||
flash.fromValue = 1
|
||||
flash.toValue = 0.1
|
||||
flash.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
flash.autoreverses = true
|
||||
flash.repeatCount = 3
|
||||
|
||||
layer.add(flash, forKey: nil)
|
||||
}
|
||||
|
||||
// MARK: - Change Color
|
||||
|
||||
|
||||
func animationOpacity(duration: CGFloat, value: CGFloat, beginTime: CGFloat = 0.0) {
|
||||
//
|
||||
let pulseAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))// "opacity")
|
||||
pulseAnimation.fromValue = 0
|
||||
pulseAnimation.toValue = 1 // 0.1
|
||||
pulseAnimation.duration = 100
|
||||
|
||||
pulseAnimation.isRemovedOnCompletion = false
|
||||
pulseAnimation.beginTime = CACurrentMediaTime() + beginTime //CACurrentMediaTime() + AVCoreAnimationBeginTimeAtZero + 1.0
|
||||
pulseAnimation.fillMode = .both
|
||||
pulseAnimation.isAdditive = false
|
||||
// pulseAnimation.repeatCount = .infinit
|
||||
layer.add(pulseAnimation, forKey: "opacityIn")
|
||||
|
||||
|
||||
|
||||
let animtingLayer: CALayer = CALayer(layer: layer)
|
||||
|
||||
|
||||
let pulseAnimation2 = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))// "opacity")
|
||||
pulseAnimation2.fromValue = 1
|
||||
pulseAnimation2.toValue = beginTime // 0.1
|
||||
pulseAnimation2.duration = 100
|
||||
pulseAnimation2.isRemovedOnCompletion = false
|
||||
pulseAnimation2.beginTime = 1.0// AVCoreAnimationBeginTimeAtZero + 0.0
|
||||
pulseAnimation2.fillMode = .both
|
||||
pulseAnimation2.isAdditive = false
|
||||
// pulseAnimation2.repeatCount = .infinit
|
||||
|
||||
|
||||
animtingLayer.add(pulseAnimation2, forKey: "opacityOut")
|
||||
layer.addSublayer(animtingLayer)
|
||||
|
||||
|
||||
layer.opacity = 0.0
|
||||
|
||||
}
|
||||
|
||||
func animationColor(duration: Speed = .s1, colors: [UIColor]) {
|
||||
|
||||
// layer.removeAnimation(forKey: "backgroundColor")
|
||||
// layer.sublayers?.forEach {
|
||||
// $0.mask?.removeAnimation(forKey: "backgroundColor")
|
||||
// }
|
||||
// layer.removeAllAnimations()
|
||||
// layer.removeFromSuperlayer()
|
||||
let darkerColor = colors[0]
|
||||
let lighterColor = colors[1]
|
||||
let pulseAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.backgroundColor))
|
||||
pulseAnimation.fromValue = darkerColor
|
||||
pulseAnimation.toValue = lighterColor
|
||||
pulseAnimation.duration = 1
|
||||
pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
pulseAnimation.autoreverses = true
|
||||
pulseAnimation.repeatCount = .infinity
|
||||
pulseAnimation.isRemovedOnCompletion = false
|
||||
|
||||
// let color = CABasicAnimation(keyPath: "backgroundColor")
|
||||
// color.fromValue = colors[0]// UIColor.green.cgColor
|
||||
// color.toValue = colors[1] // UIColor.red.cgColor
|
||||
// color.duration = duration.rawValue
|
||||
// color.beginTime = CACurrentMediaTime() + 0.3
|
||||
//// color.autoreverses = true
|
||||
//// color.repeatDuration = 1
|
||||
// color.autoreverses = true
|
||||
// color.repeatCount = 500
|
||||
// layer.cornerRadius = self.layer.cornerRadius
|
||||
//// layer.add(color, forKey: "animationColor")
|
||||
|
||||
|
||||
|
||||
layer.removeAnimation(forKey: "animationColor")
|
||||
layer.add(pulseAnimation, forKey: "animationColor")
|
||||
backgroundColor = .clear
|
||||
}
|
||||
|
||||
// MARK: - Pulsate
|
||||
|
||||
func pulsate(duration: Speed = .ms200, value: CGFloat = 0.9) {
|
||||
|
||||
let pulse = CASpringAnimation(keyPath: "transform.scale")
|
||||
pulse.duration = duration.rawValue
|
||||
pulse.fromValue = value
|
||||
pulse.toValue = 1.0
|
||||
pulse.autoreverses = true
|
||||
pulse.repeatCount = 2
|
||||
pulse.initialVelocity = 0.5
|
||||
pulse.damping = 1.0
|
||||
|
||||
layer.add(pulse, forKey: "pulse")
|
||||
}
|
||||
|
||||
// MARK: - Pulsate 2
|
||||
|
||||
func pulsateNew(visible: Alpha, value: CGFloat = 0.9) {
|
||||
if visible == .visible {
|
||||
animate(self, transform: .identity)
|
||||
} else {
|
||||
animate(self, transform: CGAffineTransform.identity.scaledBy(x: value, y: value))
|
||||
}
|
||||
}
|
||||
|
||||
private func animate(_ view: UIView, transform: CGAffineTransform) {
|
||||
UIView.animate(withDuration: 0.4,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 0.5,
|
||||
initialSpringVelocity: 3,
|
||||
options: [.curveEaseInOut],
|
||||
animations: {
|
||||
view.transform = transform
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - Shake
|
||||
|
||||
func shake() {
|
||||
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||
animation.duration = 0.6
|
||||
animation.values = [-20.0, 20.0, -20.0, 20.0, -10.0, 10.0, -5.0, 5.0, 0.0 ]
|
||||
layer.add(animation, forKey: "shake")
|
||||
}
|
||||
|
||||
// MARK: - Shake 2
|
||||
|
||||
func shakeNew(duration: Speed = .ms50) {
|
||||
|
||||
let shake = CABasicAnimation(keyPath: "position")
|
||||
shake.duration = duration.rawValue
|
||||
shake.repeatCount = 2
|
||||
shake.autoreverses = true
|
||||
|
||||
let fromPoint = CGPoint(x: center.x - 5, y: center.y)
|
||||
let fromValue = NSValue(cgPoint: fromPoint)
|
||||
|
||||
let toPoint = CGPoint(x: center.x + 5, y: center.y)
|
||||
let toValue = NSValue(cgPoint: toPoint)
|
||||
|
||||
shake.fromValue = fromValue
|
||||
shake.toValue = toValue
|
||||
|
||||
layer.add(shake, forKey: "position")
|
||||
}
|
||||
|
||||
// MARK: - Android Pulse
|
||||
|
||||
func androidPulseAnimation( duration: Speed = .ms1, color: UIColor, position: CGPoint) {
|
||||
|
||||
let pulse = PulseAnimation(numberOfPulse: 1, radius: 200, postion: position)
|
||||
pulse.animationDuration = duration.rawValue
|
||||
pulse.backgroundColor = color.cgColor // #colorLiteral(red: 0.05282949957, green: 0.5737867104, blue: 1, alpha: 1)
|
||||
layer.insertSublayer(pulse, below: layer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class PulseAnimation: CALayer {
|
||||
|
||||
var animationGroup = CAAnimationGroup()
|
||||
var animationDuration: TimeInterval = 1.5
|
||||
var radius: CGFloat = 200
|
||||
var numebrOfPulse: Float = Float.infinity
|
||||
|
||||
override init(layer: Any) {
|
||||
super.init(layer: layer)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
init(numberOfPulse: Float = Float.infinity, radius: CGFloat, postion: CGPoint){
|
||||
super.init()
|
||||
self.backgroundColor = UIColor.black.cgColor
|
||||
self.contentsScale = UIScreen.main.scale
|
||||
self.opacity = 0
|
||||
self.radius = radius
|
||||
self.numebrOfPulse = numberOfPulse
|
||||
self.position = postion
|
||||
|
||||
self.bounds = CGRect(x: 0, y: 0, width: radius*2, height: radius*2)
|
||||
self.cornerRadius = radius
|
||||
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
self.setupAnimationGroup()
|
||||
DispatchQueue.main.async {
|
||||
self.add(self.animationGroup, forKey: "pulse")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scaleAnimation() -> CABasicAnimation {
|
||||
let scaleAnimaton = CABasicAnimation(keyPath: "transform.scale.xy")
|
||||
scaleAnimaton.fromValue = NSNumber(value: 0)
|
||||
scaleAnimaton.toValue = NSNumber(value: 1)
|
||||
scaleAnimaton.duration = animationDuration
|
||||
return scaleAnimaton
|
||||
}
|
||||
|
||||
func createOpacityAnimation() -> CAKeyframeAnimation {
|
||||
let opacityAnimiation = CAKeyframeAnimation(keyPath: "opacity")
|
||||
opacityAnimiation.duration = animationDuration
|
||||
opacityAnimiation.values = [0.4,0.8,0]
|
||||
opacityAnimiation.keyTimes = [0,0.3,1]
|
||||
return opacityAnimiation
|
||||
}
|
||||
|
||||
func setupAnimationGroup() {
|
||||
self.animationGroup.duration = animationDuration
|
||||
self.animationGroup.repeatCount = numebrOfPulse
|
||||
let defaultCurve = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default)
|
||||
self.animationGroup.timingFunction = defaultCurve
|
||||
self.animationGroup.animations = [scaleAnimation(),createOpacityAnimation()]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// UIButton+Extention.swift
|
||||
// FigmaConvertXib
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 06.11.2021.
|
||||
// Copyright © 2021 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class Button: UIButton {
|
||||
|
||||
var onPressed: (() -> Void)?
|
||||
var onReleased: (() -> Void)?
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
addTarget(self, action: #selector(self.pressed), for: .touchDown)
|
||||
addTarget(self, action: #selector(self.released), for: .touchUpInside)
|
||||
addTarget(self, action: #selector(self.released), for: .touchUpOutside)
|
||||
setup()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) { fatalError() }
|
||||
|
||||
@objc func setup() {}
|
||||
|
||||
@objc func pressed() {
|
||||
onPressed?()
|
||||
}
|
||||
|
||||
@objc func released() {
|
||||
onReleased?()
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple tap implementation
|
||||
typealias Closure = () -> ()
|
||||
typealias ClosureClick = (_ event: UIControl.Event) -> ()
|
||||
|
||||
|
||||
class ClosureSleeve {
|
||||
let closure: Closure
|
||||
init(_ closure: @escaping Closure) {
|
||||
self.closure = closure
|
||||
}
|
||||
@objc func invoke () {
|
||||
closure()
|
||||
}
|
||||
}
|
||||
|
||||
extension UIControl {
|
||||
func tap(for controlEvents: UIControl.Event = .touchUpInside,
|
||||
_ closure: @escaping Closure) {
|
||||
let sleeve = ClosureSleeve(closure)
|
||||
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
|
||||
objc_setAssociatedObject(self,
|
||||
String(format: "[%d]", arc4random()),
|
||||
sleeve,
|
||||
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
|
||||
func tapClassic(_ closure: @escaping ClosureClick) {
|
||||
self.tap(for: .touchDown) { // [weak self] in
|
||||
print(" btn tapClassic touchDown")
|
||||
closure(.touchDown)
|
||||
}
|
||||
self.tap(for: .touchUpInside) { // [weak self] in
|
||||
print(" btn tapClassic touchUpInside")
|
||||
closure(.touchUpInside)
|
||||
}
|
||||
self.tap(for: .touchUpOutside) { // [weak self] in
|
||||
print(" btn tapClassic touchUpOutside")
|
||||
closure(.touchUpOutside)
|
||||
}
|
||||
self.tap(for: .touchCancel) { // [weak self] in
|
||||
print(" btn tapClassic touchCancel")
|
||||
closure(.touchCancel)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
//
|
||||
// ViewAnimationSelect.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 22.06.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class ButtonAnimationView: UIView {
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
@IBInspectable var animationType: Int = 0
|
||||
|
||||
public var type: UIButton.TapType = .alpha(0.5)
|
||||
private var subviewsAnimation = false
|
||||
public var button: UIButton?
|
||||
|
||||
// MARK: - Initialize
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
updateSubviews()
|
||||
}
|
||||
|
||||
public init() {
|
||||
super.init(frame: .zero)
|
||||
updateSubviews()
|
||||
}
|
||||
|
||||
override func layoutSubviews() { updateSubviews() }
|
||||
|
||||
public func setAnimation(type: UIButton.TapType, views: [UIView]? = nil) {
|
||||
self.type = type
|
||||
// updateSubviews()
|
||||
|
||||
layer.cornerRadius = cornerRadius
|
||||
|
||||
var nviews: [UIView] = []
|
||||
if let views = views {
|
||||
nviews = views
|
||||
} else {
|
||||
nviews.append(self)
|
||||
subviews.forEach {
|
||||
nviews.append($0)
|
||||
}
|
||||
}
|
||||
|
||||
button?.tapHideAnimation(views: nviews, type: type, callback: { type in
|
||||
if type == .touchUpInside {
|
||||
|
||||
}
|
||||
})
|
||||
subviewsAnimation = true
|
||||
}
|
||||
|
||||
public func updateSubviews() {
|
||||
|
||||
if button == nil {
|
||||
createButton()
|
||||
}
|
||||
|
||||
|
||||
if animationType != 0 {
|
||||
|
||||
let type: UIButton.TapType = {
|
||||
switch animationType {
|
||||
case 1: return .alpha(0.5)
|
||||
case 2: return .layerGray(0.1845)
|
||||
case 3: return .pulsate(new: false, value: 0.9)
|
||||
case 4: return .pulsate(new: true, value: 0.9)
|
||||
case 5: return .shake(new: false)
|
||||
case 6: return .shake(new: true)
|
||||
case 7: return .flash
|
||||
case 8: return .color([.white, .red])
|
||||
case 9: return .androidStyle(color: .white)
|
||||
default: return .alpha(0.5)
|
||||
}
|
||||
}()
|
||||
|
||||
setAnimation(type: type)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func createButton() {
|
||||
let btn = UIButton(type: .system)
|
||||
btn.frame = bounds
|
||||
btn.backgroundColor = .clear
|
||||
btn.setTitle(nil, for: .normal)
|
||||
btn.contentVerticalAlignment = .center
|
||||
btn.autoresizingMask = [ .flexibleTopMargin,
|
||||
.flexibleBottomMargin,
|
||||
.flexibleLeftMargin,
|
||||
.flexibleRightMargin,
|
||||
.flexibleWidth,
|
||||
.flexibleHeight ]
|
||||
addSubview(btn)
|
||||
self.button = btn
|
||||
}
|
||||
}
|
||||
|
||||
extension UIButton {
|
||||
|
||||
// MARK: Tap Animation Type
|
||||
|
||||
static let setTag = 963
|
||||
|
||||
enum TapType {
|
||||
case alpha(_ value: CGFloat) // 0.5
|
||||
case layerGray(_ value: CGFloat = 0.1845) // 0.1845 add black shadow
|
||||
case pulsate(new: Bool = false, value: CGFloat = 0.9)
|
||||
case shake(new: Bool = false)
|
||||
case flash
|
||||
case color(_ arr: [UIColor] = [.white, .red])
|
||||
case androidStyle(color: UIColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1))
|
||||
}
|
||||
|
||||
// MARK: Set
|
||||
|
||||
func tapHideAnimation(
|
||||
view: UIView?,
|
||||
type: TapType = .alpha(0.0),
|
||||
hideSpeed: Speed? = .ms100,
|
||||
callback: ((UIControl.Event) -> Void)? = nil
|
||||
) {
|
||||
guard let view = view else { return }
|
||||
tapHideAnimation(views: [view], type: type, callback: callback)
|
||||
}
|
||||
|
||||
func tapHideAnimation(
|
||||
views: [UIView],
|
||||
type: TapType = .alpha(0.0),
|
||||
hideSpeed: Speed? = .ms100,
|
||||
callback: ((UIControl.Event) -> Void)? = nil
|
||||
) {
|
||||
|
||||
switch type {
|
||||
case .androidStyle(let color): do {
|
||||
|
||||
var tapAdded: Bool = false
|
||||
gestureRecognizers?.forEach {
|
||||
if $0 is BindableGestureRecognizer {
|
||||
tapAdded = true
|
||||
}
|
||||
}
|
||||
if !tapAdded {
|
||||
|
||||
let tapp = BindableGestureRecognizer { [weak self] sender in
|
||||
|
||||
guard let superview = self?.superview else { return }
|
||||
let touchPoint = sender.location(in: superview)
|
||||
|
||||
guard let dur: Speed = self?.defaultDuration(type: type) else { return }
|
||||
self?.androidPulseAnimation(duration: dur, color: color, position: touchPoint)
|
||||
|
||||
|
||||
if sender.state == .began {
|
||||
callback?(.touchDown)
|
||||
} else if sender.state == .changed {
|
||||
callback?(.touchDown)
|
||||
} else if sender.state == .ended {
|
||||
callback?(.touchUpInside)
|
||||
}
|
||||
// callback?(.touchUpInside)
|
||||
}
|
||||
addGestureRecognizer(tapp)
|
||||
}
|
||||
}
|
||||
default: break
|
||||
}
|
||||
|
||||
|
||||
// if tag != UIButton.setTag { return }
|
||||
// tag = UIButton.setTag
|
||||
|
||||
tap(for: .touchDown) { [weak self] in
|
||||
callback?(.touchDown)
|
||||
self?.hideAnimation(with: views, type: type, event: .touchDown, duration: hideSpeed)
|
||||
}
|
||||
tap(for: .touchUpInside) { [weak self] in
|
||||
callback?(.touchUpInside)
|
||||
self?.hideAnimation(with: views, visible: .visible, type: type, event: .touchUpInside)
|
||||
}
|
||||
tap(for: .touchUpOutside) { [weak self] in
|
||||
callback?(.touchUpOutside)
|
||||
self?.hideAnimation(with: views, visible: .visible, type: type, event: .touchUpOutside)
|
||||
}
|
||||
tap(for: .touchCancel) { [weak self] in
|
||||
callback?(.touchCancel)
|
||||
self?.hideAnimation(with: views, visible: .visible, type: type, event: .touchCancel)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - De-Select
|
||||
|
||||
func deselected(_ views: [UIView]) {
|
||||
hideAnimation(
|
||||
with: views,
|
||||
visible: .visible
|
||||
)
|
||||
}
|
||||
|
||||
func defaultDuration(type: TapType) -> Speed {
|
||||
switch type {
|
||||
case .alpha(_), .layerGray(_): return .ms300
|
||||
case .pulsate(_, _), .flash: return .ms200
|
||||
case .shake(_): return .ms50
|
||||
case .color(_), .androidStyle(_): return .s1
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Animation Filter
|
||||
|
||||
func hideAnimation(
|
||||
with views: [UIView],
|
||||
visible: Alpha = .invisible,
|
||||
type: TapType = .alpha(0.0),
|
||||
event: UIControl.Event = .touchUpInside,
|
||||
duration: Speed? = nil
|
||||
) {
|
||||
|
||||
let dur: Speed = defaultDuration(type: type)
|
||||
switch type {
|
||||
case .alpha(let value):
|
||||
updateChangeAlpha(with: views, visible: visible, value: value, duration: dur)
|
||||
case .layerGray(let value):
|
||||
if event == .touchDown {
|
||||
updateLayerGray(with: views, visible: .invisible, value: value, duration: nil)
|
||||
} else {
|
||||
updateLayerGray(with: views, visible: .visible, value: value, duration: dur)
|
||||
}
|
||||
default: break
|
||||
}
|
||||
|
||||
views.forEach {
|
||||
switch type {
|
||||
case .flash: $0.flash(duration: dur)
|
||||
case .color(let arr): $0.animationColor(duration: dur, colors: arr)
|
||||
case .pulsate(let new, let value):
|
||||
if new { $0.pulsateNew(visible: visible, value: value) }
|
||||
else { $0.pulsate(duration: dur, value: value) }
|
||||
case .shake(let new):
|
||||
if new { $0.shake() }
|
||||
else { $0.shakeNew(duration: dur) }
|
||||
// case .androidClickable:
|
||||
// androidPulseAnimation(duration: dur)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension UIControl.Event {
|
||||
|
||||
func strName() -> String {
|
||||
return enevtName(self)
|
||||
}
|
||||
|
||||
func enevtName(_ event: UIControl.Event) -> String {
|
||||
switch event {
|
||||
case .touchDown: return "touch.Down"
|
||||
case .touchCancel: return "touch.Cancel"
|
||||
case .touchUpOutside: return "touch.Up.Outside"
|
||||
case .touchUpInside: return "touch.Up.Inside"
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias Interval = TimeInterval
|
||||
|
||||
enum Speed: Interval {
|
||||
case zero = 0.0
|
||||
case ms1 = 0.001
|
||||
case ms50 = 0.05
|
||||
case ms100 = 0.1
|
||||
case ms200 = 0.2
|
||||
case ms300 = 0.3
|
||||
case ms350 = 0.35
|
||||
case ms400 = 0.4
|
||||
case ms450 = 0.45
|
||||
case ms500 = 0.5
|
||||
case s1 = 1.0
|
||||
case s2 = 2.0
|
||||
case s3 = 3.0
|
||||
case s4 = 4.0
|
||||
case s5 = 5.0
|
||||
}
|
||||
|
||||
enum Delay: Interval {
|
||||
case ms1 = 0.001
|
||||
case ms100 = 0.1
|
||||
case ms200 = 0.2
|
||||
case ms300 = 0.3
|
||||
case ms400 = 0.4
|
||||
case ms500 = 0.5
|
||||
case s1 = 1.0
|
||||
case s2 = 2.0
|
||||
case s3 = 3.0
|
||||
case s4 = 4.0
|
||||
case s5 = 5.0
|
||||
}
|
||||
|
||||
|
||||
enum Alpha: CGFloat {
|
||||
case invisible = 0.0
|
||||
case halfTransluent = 0.5
|
||||
case visible = 1.0
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
func setAlpha(_ alpha: Alpha) {
|
||||
self.alpha = alpha.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//MARK: - Override animations
|
||||
extension UIView {
|
||||
|
||||
static func animate(with duration: Speed,
|
||||
animations: @escaping () -> Void) {
|
||||
UIView.animate(withDuration: duration.rawValue, animations: animations)
|
||||
}
|
||||
|
||||
static func animate(with duration: Speed,
|
||||
animations: @escaping () -> Void,
|
||||
completion: ((Bool) -> Void)?) {
|
||||
UIView.animate(withDuration: duration.rawValue,
|
||||
animations: animations,
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
static func animate(with duration: Speed,
|
||||
delay: Delay,
|
||||
options: AnimationOptions,
|
||||
animations: @escaping () -> Void,
|
||||
completion: ((Bool) -> Void)?) {
|
||||
UIView.animate(withDuration: duration.rawValue,
|
||||
delay: delay.rawValue,
|
||||
options: options,
|
||||
animations: animations,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
final class BindableGestureRecognizer: UITapGestureRecognizer {
|
||||
private var action: (UITapGestureRecognizer) -> Void
|
||||
|
||||
init(action: @escaping (UITapGestureRecognizer) -> Void) {
|
||||
self.action = action
|
||||
super.init(target: nil, action: nil)
|
||||
self.addTarget(self, action: #selector(execute(_:)))
|
||||
}
|
||||
|
||||
@objc private func execute(_ sender: UITapGestureRecognizer) {
|
||||
action(sender)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
|
||||
import UIKit
|
||||
|
||||
class CopyableLabel: UILabel {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.sharedInit()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.sharedInit()
|
||||
}
|
||||
|
||||
func sharedInit() {
|
||||
self.isUserInteractionEnabled = true
|
||||
let gesture = UILongPressGestureRecognizer(target: self, action: #selector(self.showMenu))
|
||||
self.addGestureRecognizer(gesture)
|
||||
}
|
||||
|
||||
@objc func showMenu(_ recognizer: UILongPressGestureRecognizer) {
|
||||
self.becomeFirstResponder()
|
||||
|
||||
let menu = UIMenuController.shared
|
||||
|
||||
let locationOfTouchInLabel = recognizer.location(in: self)
|
||||
|
||||
if !menu.isMenuVisible {
|
||||
var rect = bounds
|
||||
rect.origin = locationOfTouchInLabel
|
||||
rect.size = CGSize(width: 1, height: 1)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
menu.showMenu(from: self, rect: rect)
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func copy(_ sender: Any?) {
|
||||
let board = UIPasteboard.general
|
||||
|
||||
board.string = text
|
||||
|
||||
let menu = UIMenuController.shared
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
menu.showMenu(from: self, rect: bounds)
|
||||
} else {
|
||||
menu.setMenuVisible(false, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||
return action == #selector(UIResponderStandardEditActions.copy)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class DesignButton: UIButton {
|
||||
|
||||
@IBInspectable var hideAnimation: Bool = true
|
||||
|
||||
//MARK: Fill
|
||||
|
||||
@IBInspectable var fillColor: UIColor = .clear
|
||||
|
||||
//MARK: Gradient
|
||||
|
||||
@IBInspectable var grColor1: UIColor?
|
||||
@IBInspectable var grColor2: UIColor?
|
||||
@IBInspectable var grColor3: UIColor?
|
||||
@IBInspectable var grColor4: UIColor?
|
||||
@IBInspectable var grColor5: UIColor?
|
||||
@IBInspectable var grColor6: UIColor?
|
||||
|
||||
@IBInspectable var grStartPoint: CGPoint = .zero
|
||||
@IBInspectable var grEndPoint: CGPoint = .zero
|
||||
|
||||
//MARK: Inner Shadow
|
||||
|
||||
@IBInspectable var inShColor: UIColor = .clear
|
||||
@IBInspectable var inShRadius: CGFloat = 0.0
|
||||
@IBInspectable var inShOffset: CGSize = .zero
|
||||
|
||||
//MARK: Shadow
|
||||
|
||||
@IBInspectable var shColor: UIColor = .clear
|
||||
@IBInspectable var shRadius: CGFloat = 0.0
|
||||
@IBInspectable var shOffset: CGSize = .zero
|
||||
|
||||
//MARK: Border
|
||||
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
|
||||
//MARK: Blur
|
||||
|
||||
@IBInspectable var blur: CGFloat = 0.0
|
||||
|
||||
//MARK: Radius
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
|
||||
layer.cornerRadius = self.cornerRadius(cornerRadius)
|
||||
layer.backgroundColor = fillColor.cgColor
|
||||
|
||||
addGradient()
|
||||
addInnerShadow()
|
||||
addShadow()
|
||||
addBorder()
|
||||
|
||||
add(blur: blur)
|
||||
}
|
||||
|
||||
private func addGradient() {
|
||||
|
||||
_ = add(gradient: grColor1,
|
||||
color2: grColor2,
|
||||
color3: grColor3,
|
||||
color4: grColor4,
|
||||
color5: grColor5,
|
||||
color6: grColor6,
|
||||
pointStart: grStartPoint,
|
||||
pointEnd: grEndPoint,
|
||||
cornerRadius: cornerRadius)
|
||||
|
||||
}
|
||||
|
||||
private func addInnerShadow() {
|
||||
|
||||
addInnerShadow(color: inShColor,
|
||||
radius: inShRadius,
|
||||
offset: inShOffset,
|
||||
cornerRadius: cornerRadius)
|
||||
|
||||
}
|
||||
|
||||
private func addShadow() {
|
||||
layer.shadowOffset = shOffset
|
||||
layer.shadowOpacity = 1.0
|
||||
layer.shadowRadius = shRadius
|
||||
layer.shadowColor = shColor.cgColor
|
||||
}
|
||||
|
||||
private func addBorder() {
|
||||
layer.borderColor = brColor.cgColor
|
||||
layer.borderWidth = brWidth
|
||||
}
|
||||
|
||||
override var isSelected: Bool {
|
||||
didSet {
|
||||
super.isSelected = isSelected
|
||||
}
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
didSet {
|
||||
// super.isHighlighted = isHighlighted
|
||||
}
|
||||
}
|
||||
|
||||
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
selectedLayer(show: true)
|
||||
return super.beginTracking(touch, with: event)
|
||||
}
|
||||
|
||||
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
let point = touch.location(in: self)
|
||||
let someFrame = bounds
|
||||
let highlighted = someFrame.contains(point)
|
||||
selectedLayer(show: highlighted)
|
||||
return super.continueTracking(touch, with: event)
|
||||
}
|
||||
|
||||
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||
selectedLayer(show: false)
|
||||
super.endTracking(touch, with: event)
|
||||
}
|
||||
|
||||
override func cancelTracking(with event: UIEvent?) {
|
||||
selectedLayer(show: false)
|
||||
super.cancelTracking(with: event)
|
||||
}
|
||||
|
||||
func radius() -> CGFloat {
|
||||
let minSize = min(frame.size.width, frame.size.height)
|
||||
let radius = ((cornerRadius < 0) ? (minSize / 2) : cornerRadius)
|
||||
return radius
|
||||
}
|
||||
|
||||
func selectedLayer(show: Bool) {
|
||||
|
||||
func selectAnimationHide(show: Bool) {
|
||||
if show {
|
||||
setAlpha(.halfTransluent)
|
||||
} else {
|
||||
UIView.animate(with: .ms350) {
|
||||
self.setAlpha(.visible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func selectAnimationShadow(show: Bool) {
|
||||
let tag = 1
|
||||
let view = viewWithTag(tag)
|
||||
if show {
|
||||
if view == nil {
|
||||
let view = UIView(frame: bounds)
|
||||
view.backgroundColor = UIColor.init(white: 0.0, alpha: 0.2)
|
||||
view.tag = tag
|
||||
view.layer.cornerRadius = radius()
|
||||
addSubview(view)
|
||||
}
|
||||
} else {
|
||||
if let view = view {
|
||||
UIView.animate(with: .ms350, animations: {
|
||||
view.setAlpha(.invisible)
|
||||
}, completion: { (fin: Bool) in
|
||||
view.removeFromSuperview()
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hideAnimation {
|
||||
selectAnimationHide(show: show)
|
||||
} else {
|
||||
selectAnimationShadow(show: show)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,716 @@
|
||||
//
|
||||
// DesignTest.swift
|
||||
// FigmaConvertXib
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 28.07.2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
@IBDesignable
|
||||
class DesignFigure: UIView {
|
||||
/// Rectange | Ellipse | Polygon | Star
|
||||
@IBInspectable var figureType: Int = 0
|
||||
@IBInspectable var starRadius: CGFloat = 0
|
||||
@IBInspectable var starCount: Int = 5
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
@IBInspectable var blur: CGFloat = 0.0
|
||||
@IBInspectable var image: UIImage?
|
||||
/// ScaleToFill | Fit | Fill
|
||||
@IBInspectable var imageMode: Int = 1
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
@IBInspectable var grColor1: UIColor?
|
||||
@IBInspectable var grColor2: UIColor?
|
||||
@IBInspectable var grColor3: UIColor?
|
||||
@IBInspectable var grColor4: UIColor?
|
||||
@IBInspectable var grColor5: UIColor?
|
||||
@IBInspectable var grColor6: UIColor?
|
||||
@IBInspectable var grStartPoint: CGPoint = .zero
|
||||
@IBInspectable var grEndPoint: CGPoint = .zero
|
||||
/// Default: Linear
|
||||
@IBInspectable var grRadial: Bool = false
|
||||
@IBInspectable var grDrawsOptions: Bool = true
|
||||
@IBInspectable var grDebug: Bool = false
|
||||
@IBInspectable var grPointPercent: Bool = true
|
||||
@IBInspectable var grBlendMode: Int = 0
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
@IBInspectable var brDash: Int = 0
|
||||
@IBInspectable var inShColor: UIColor = .clear
|
||||
@IBInspectable var inShRadius: CGFloat = 0.0
|
||||
@IBInspectable var inShOffset: CGSize = .zero
|
||||
@IBInspectable var shColor: UIColor = .clear
|
||||
@IBInspectable var shRadius: CGFloat = 0.0
|
||||
@IBInspectable var shOffset: CGSize = .zero
|
||||
|
||||
func setup() {
|
||||
|
||||
if let foundView = viewWithTag(55) {
|
||||
foundView.removeFromSuperview()
|
||||
}
|
||||
|
||||
let d = DesignFigure_(frame: bounds)
|
||||
d.backgroundColor = .clear
|
||||
d.tag = 55
|
||||
d.figureType = figureType
|
||||
d.starRadius = starRadius
|
||||
d.starCount = starCount
|
||||
d.cornerRadius = cornerRadius
|
||||
d.blur = blur
|
||||
d.image = image
|
||||
d.imageMode = imageMode
|
||||
d.fillColor = fillColor
|
||||
d.grColor1 = grColor1
|
||||
d.grColor2 = grColor2
|
||||
d.grColor3 = grColor3
|
||||
d.grColor4 = grColor4
|
||||
d.grColor5 = grColor5
|
||||
d.grColor6 = grColor6
|
||||
d.grStartPoint = grStartPoint
|
||||
d.grEndPoint = grEndPoint
|
||||
d.grRadial = grRadial
|
||||
d.grDrawsOptions = grDrawsOptions
|
||||
d.grDebug = grDebug
|
||||
d.grPointPercent = grPointPercent
|
||||
d.grBlendMode = grBlendMode
|
||||
d.brWidth = brWidth
|
||||
d.brColor = brColor
|
||||
d.brDash = brDash
|
||||
d.inShColor = inShColor
|
||||
d.inShRadius = inShRadius
|
||||
d.inShOffset = inShOffset
|
||||
d.shColor = shColor
|
||||
d.shRadius = shRadius
|
||||
d.shOffset = shOffset
|
||||
insertSubview(d, at: 0)
|
||||
|
||||
if grDebug {
|
||||
if let foundView = viewWithTag(44) {
|
||||
foundView.removeFromSuperview()
|
||||
}
|
||||
|
||||
let d = DesignFigureDebug(frame: bounds)
|
||||
d.backgroundColor = .clear
|
||||
d.tag = 44
|
||||
d.grStartPoint = grStartPoint
|
||||
d.grEndPoint = grEndPoint
|
||||
d.grPointPercent = grPointPercent
|
||||
|
||||
insertSubview(d, at: 1)
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() { setup() }
|
||||
|
||||
}
|
||||
|
||||
@IBDesignable
|
||||
class DesignFigureDebug: UIView {
|
||||
|
||||
@IBInspectable var grStartPoint: CGPoint = .zero
|
||||
@IBInspectable var grEndPoint: CGPoint = .zero
|
||||
@IBInspectable var grPointPercent: Bool = true
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
|
||||
addGradientDebugPoints()
|
||||
}
|
||||
|
||||
func addGradientDebugPoints() {
|
||||
|
||||
var start = grStartPoint
|
||||
var end = grStartPoint
|
||||
|
||||
if grPointPercent {
|
||||
end = CGPoint(x: grEndPoint.x * frame.width, y: grEndPoint.y * frame.height)
|
||||
start = CGPoint(x: grStartPoint.x * frame.width, y: grStartPoint.y * frame.height)
|
||||
}
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
let radius: CGFloat = 5
|
||||
|
||||
context.setBlendMode(CGBlendMode.normal)
|
||||
context.setLineWidth(3.0)
|
||||
|
||||
|
||||
context.setFillColor(UIColor.black.cgColor)
|
||||
|
||||
context.setStrokeColor(UIColor.systemRed.cgColor)
|
||||
|
||||
let circleRect = CGRect(x: start.x - radius,
|
||||
y: start.y - radius,
|
||||
width: radius * 2,
|
||||
height: radius * 2)
|
||||
|
||||
context.fillEllipse(in: circleRect)
|
||||
context.strokeEllipse(in: circleRect)
|
||||
|
||||
context.setStrokeColor(UIColor.green.cgColor)
|
||||
|
||||
let circleRect2 = CGRect(x: end.x - radius,
|
||||
y: end.y - radius,
|
||||
width: radius * 2,
|
||||
height: radius * 2)
|
||||
|
||||
context.fillEllipse(in: circleRect2)
|
||||
context.strokeEllipse(in: circleRect2)
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum DesignFigureType: Int {
|
||||
case rectange = 0
|
||||
case ellipse = 1
|
||||
case polygon = 2
|
||||
case star = 3
|
||||
}
|
||||
|
||||
@IBDesignable
|
||||
class DesignFigure_: UIView {
|
||||
|
||||
@IBInspectable var figureType: Int = 0 /// Rectange Ellipse Polygon Star
|
||||
|
||||
@IBInspectable var starRadius: CGFloat = 0
|
||||
@IBInspectable var starCount: Int = 5
|
||||
//MARK: Radius
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
//MARK: Blur
|
||||
|
||||
@IBInspectable var blur: CGFloat = 0.0
|
||||
|
||||
//MARK: Image
|
||||
|
||||
@IBInspectable var image: UIImage?
|
||||
@IBInspectable var imageMode: Int = 1 /// Scale to fill / Aspect fit / fill
|
||||
|
||||
//MARK: Fill
|
||||
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
|
||||
//MARK: Gradient
|
||||
|
||||
@IBInspectable var grColor1: UIColor?
|
||||
@IBInspectable var grColor2: UIColor?
|
||||
@IBInspectable var grColor3: UIColor?
|
||||
@IBInspectable var grColor4: UIColor?
|
||||
@IBInspectable var grColor5: UIColor?
|
||||
@IBInspectable var grColor6: UIColor?
|
||||
|
||||
@IBInspectable var grStartPoint: CGPoint = .zero
|
||||
@IBInspectable var grEndPoint: CGPoint = .zero
|
||||
|
||||
@IBInspectable var grRadial: Bool = false /// default: linear
|
||||
@IBInspectable var grDrawsOptions: Bool = true
|
||||
@IBInspectable var grDebug: Bool = false
|
||||
@IBInspectable var grPointPercent: Bool = true
|
||||
@IBInspectable var grBlendMode: Int = 0
|
||||
|
||||
//MARK: Border
|
||||
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
@IBInspectable var brDash: Int = 0
|
||||
|
||||
//MARK: Inner Shadow
|
||||
|
||||
@IBInspectable var inShColor: UIColor = .clear
|
||||
@IBInspectable var inShRadius: CGFloat = 0.0
|
||||
@IBInspectable var inShOffset: CGSize = .zero
|
||||
|
||||
//MARK: Shadow
|
||||
|
||||
@IBInspectable var shColor: UIColor = .clear
|
||||
@IBInspectable var shRadius: CGFloat = 0.0
|
||||
@IBInspectable var shOffset: CGSize = .zero
|
||||
|
||||
|
||||
|
||||
//MARK: - Draw
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
|
||||
guard let bezier = figurePath(bounds) else { return }
|
||||
|
||||
addFill(bezier: bezier)
|
||||
|
||||
addImage(bezier: bezier)
|
||||
|
||||
addGradient(bezier: bezier)
|
||||
|
||||
addInnerShadow(bezier: bezier)
|
||||
addBorder()
|
||||
addShadow()
|
||||
|
||||
addBlur()
|
||||
}
|
||||
|
||||
//MARK: - Figure Type
|
||||
|
||||
private func figurePath(_ rect: CGRect) -> UIBezierPath? {
|
||||
|
||||
let starR = starRadius / 50
|
||||
if starCount < 1 { starCount = 1 }
|
||||
|
||||
switch figureType {
|
||||
case 1: return UIBezierPath(ovalIn: rect)
|
||||
case 2: return star(polygon: true, rect: rect, radius: starR, pointsOnStar: starCount)
|
||||
case 3: return star(rect: rect, radius: starR, pointsOnStar: starCount)
|
||||
default: return rectangle(rect: rect, radius: cornerRadius)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Border / Stroke
|
||||
|
||||
private func addBorder() {
|
||||
|
||||
if brWidth < 0 { brWidth = 0 }
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
// brColor.setStroke()
|
||||
|
||||
guard let bezier = figurePath(bounds) else { return }
|
||||
|
||||
context.saveGState()
|
||||
|
||||
if brDash > 0 {
|
||||
|
||||
UIColor.clear.setFill()
|
||||
bezier.fill()
|
||||
|
||||
brColor.setStroke()
|
||||
bezier.lineWidth = brWidth * 2
|
||||
|
||||
let dashPattern : [CGFloat] = [ CGFloat(brDash), CGFloat(brDash)]
|
||||
bezier.setLineDash(dashPattern, count: 2, phase: 0)
|
||||
bezier.stroke()
|
||||
|
||||
} else {
|
||||
|
||||
brColor.setStroke()
|
||||
|
||||
bezier.lineWidth = brWidth * 2
|
||||
bezier.stroke()
|
||||
}
|
||||
|
||||
|
||||
|
||||
bezier.stroke()
|
||||
bezier.stroke()
|
||||
|
||||
context.restoreGState()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
|
||||
private func addImage(bezier: UIBezierPath) {
|
||||
|
||||
guard let image = image else { return }
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
var f: CGRect = CGRect(x: 0, y: -bounds.height, width: bounds.width, height: bounds.height)
|
||||
|
||||
if imageMode == 1 || imageMode == 2 {
|
||||
|
||||
var w: CGFloat = 0
|
||||
var h: CGFloat = 0
|
||||
|
||||
let percent = bounds.height / image.size.height
|
||||
w = image.size.width * percent
|
||||
h = bounds.height
|
||||
|
||||
if (imageMode == 1) && (w > bounds.width) ||
|
||||
(imageMode == 2) && (bounds.width > w) {
|
||||
|
||||
let percent = bounds.width / image.size.width
|
||||
h = image.size.height * percent
|
||||
w = bounds.width
|
||||
}
|
||||
|
||||
let x = (bounds.width / 2) - (w / 2)
|
||||
let y = (-h + (((bounds.height / 2) - (h / 2)) * (-1)))
|
||||
|
||||
f = CGRect(x: x, y: y, width: w, height: h)
|
||||
}
|
||||
|
||||
context.saveGState()
|
||||
bezier.addClip()
|
||||
context.scaleBy(x: 1, y: -1)
|
||||
context.draw(image.cgImage!, in: f, byTiling: false)
|
||||
context.restoreGState()
|
||||
|
||||
}
|
||||
|
||||
//MARK: - Fill
|
||||
|
||||
private func addFill(bezier: UIBezierPath) {
|
||||
|
||||
guard let fillColor = fillColor else { return }
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
bezier.close()
|
||||
context.saveGState()
|
||||
fillColor.setFill()
|
||||
bezier.fill()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
//MARK: - Gradient
|
||||
|
||||
private func addGradient(bezier: UIBezierPath) {
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
var colors: [CGColor] = []
|
||||
|
||||
if let c = grColor1 { colors.append(c.cgColor) }
|
||||
if let c = grColor2 { colors.append(c.cgColor) }
|
||||
if let c = grColor3 { colors.append(c.cgColor) }
|
||||
if let c = grColor4 { colors.append(c.cgColor) }
|
||||
if let c = grColor5 { colors.append(c.cgColor) }
|
||||
if let c = grColor6 { colors.append(c.cgColor) }
|
||||
|
||||
if colors.count < 2 {
|
||||
colors.append(UIColor.clear.cgColor)
|
||||
colors.append(UIColor.clear.cgColor)
|
||||
} else if colors.count < 1 {
|
||||
if grColor1 != nil {
|
||||
colors.append(UIColor.clear.cgColor)
|
||||
} else {
|
||||
colors.insert(UIColor.clear.cgColor, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
guard let gradient = CGGradient(colorsSpace: nil,
|
||||
colors: colors as CFArray,
|
||||
locations: nil) else { return }
|
||||
|
||||
let bMode = CGBlendMode(rawValue: CGBlendMode.RawValue(grBlendMode)) ?? CGBlendMode.sourceAtop
|
||||
context.setBlendMode(bMode)
|
||||
|
||||
let options: CGGradientDrawingOptions =
|
||||
grDrawsOptions ? [ .drawsBeforeStartLocation, .drawsAfterEndLocation ] : [ ]
|
||||
|
||||
var grEndPointR: CGPoint = grEndPoint
|
||||
var grStartPointR: CGPoint = grStartPoint
|
||||
if grPointPercent {
|
||||
grEndPointR = CGPoint(x: (grEndPoint.x * frame.width), y: (grEndPoint.y * frame.height))
|
||||
grStartPointR = CGPoint(x: (grStartPoint.x * frame.width), y: (grStartPoint.y * frame.height))
|
||||
}
|
||||
|
||||
bezier.close()
|
||||
context.saveGState()
|
||||
bezier.addClip()
|
||||
|
||||
if grRadial {
|
||||
|
||||
let x: CGFloat = (grEndPointR.x - grStartPointR.x)
|
||||
let y: CGFloat = (grEndPointR.y - grStartPointR.y)
|
||||
let distance: CGFloat = sqrt((x * x) + (y * y))
|
||||
|
||||
context.drawRadialGradient(gradient,
|
||||
startCenter: grStartPointR,
|
||||
startRadius: 0,
|
||||
endCenter: grStartPointR,
|
||||
endRadius: distance,
|
||||
options: options)
|
||||
} else {
|
||||
|
||||
context.drawLinearGradient(gradient,
|
||||
start: grStartPointR,
|
||||
end: grEndPointR,
|
||||
options: options)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Shadow
|
||||
|
||||
private func addShadow() {
|
||||
|
||||
if shRadius < 0 { shRadius = 0 }
|
||||
|
||||
layer.shadowOffset = shOffset
|
||||
layer.shadowOpacity = 1.0
|
||||
layer.shadowRadius = shRadius
|
||||
layer.shadowColor = shColor.cgColor
|
||||
}
|
||||
|
||||
private func addShadowContext(bezier: UIBezierPath) {
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
bezier.close()
|
||||
|
||||
//// Shadow Declarations
|
||||
let shadow = NSShadow()
|
||||
shadow.shadowColor = shColor
|
||||
shadow.shadowOffset = shOffset
|
||||
shadow.shadowBlurRadius = shRadius * 2
|
||||
|
||||
context.saveGState()
|
||||
|
||||
context.setShadow(offset: shadow.shadowOffset,
|
||||
blur: shadow.shadowBlurRadius,
|
||||
color: (shadow.shadowColor as! UIColor).cgColor)
|
||||
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
//MARK: - Inner Shadow
|
||||
|
||||
private func addInnerShadow(bezier: UIBezierPath) {
|
||||
|
||||
if inShRadius < 0 { inShRadius = 0 }
|
||||
|
||||
let innerShadow = NSShadow()
|
||||
innerShadow.shadowColor = inShColor
|
||||
innerShadow.shadowOffset = inShOffset
|
||||
innerShadow.shadowBlurRadius = inShRadius * 2
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
////// Star Inner Shadow
|
||||
context.saveGState()
|
||||
context.clip(to: bezier.bounds)
|
||||
context.setShadow(offset: .zero, blur: 0)
|
||||
context.setAlpha((innerShadow.shadowColor as! UIColor).cgColor.alpha)
|
||||
context.beginTransparencyLayer(auxiliaryInfo: nil)
|
||||
|
||||
let starOpaqueShadow = (innerShadow.shadowColor as! UIColor).withAlphaComponent(1)
|
||||
context.setShadow(offset: innerShadow.shadowOffset,
|
||||
blur: innerShadow.shadowBlurRadius,
|
||||
color: starOpaqueShadow.cgColor)
|
||||
|
||||
context.setBlendMode(.sourceOut)
|
||||
context.beginTransparencyLayer(auxiliaryInfo: nil)
|
||||
|
||||
starOpaqueShadow.setFill()
|
||||
|
||||
bezier.fill()
|
||||
|
||||
context.endTransparencyLayer()
|
||||
context.endTransparencyLayer()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Blur
|
||||
|
||||
private func addBlur() {
|
||||
|
||||
guard blur > 0 else { return }
|
||||
|
||||
guard let image = screenShotContext() else { return }
|
||||
|
||||
self.addblur(screen: image, blur: blur)
|
||||
}
|
||||
|
||||
// MARK: Screen Shot Context
|
||||
|
||||
private func screenShotContext() -> UIImage? {
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return nil }
|
||||
guard let cgimage: CGImage = context.makeImage() else { return nil }
|
||||
let image = UIImage(cgImage: cgimage)
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
// MARK: Clear Context
|
||||
|
||||
private func clearContext() {
|
||||
|
||||
let f = CGRect(x: 0, y: 0,
|
||||
width: frame.width * 2,
|
||||
height: frame.height * 2)
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
context?.clear(f)
|
||||
}
|
||||
|
||||
|
||||
private func addblur(screen: UIImage, blur: CGFloat) {
|
||||
|
||||
clearContext()
|
||||
|
||||
func blurImage(image: UIImage) -> UIImage? {
|
||||
|
||||
guard let ciscreen: CIImage = CIImage(image: screen) else { return nil }
|
||||
|
||||
guard let filter: CIFilter = CIFilter(name: "CIGaussianBlur") else { return nil }
|
||||
filter.setDefaults()
|
||||
filter.setValue(ciscreen, forKey: kCIInputImageKey)
|
||||
filter.setValue(blur, forKey: kCIInputRadiusKey)
|
||||
|
||||
|
||||
let bl2 = ((blur * 3) * -1)
|
||||
let bl4 = (blur * 6)
|
||||
|
||||
let contextFrame = CGRect(
|
||||
x: bl2,
|
||||
y: bl2,
|
||||
width: bl4 + image.size.width,
|
||||
height: bl4 + image.size.height
|
||||
)
|
||||
|
||||
// let bl2 = ((blur * 2) * -1)
|
||||
// let bl4 = (blur * 4)
|
||||
// let contextFrame2 = CGRect(x: bl2,
|
||||
// y: bl2,
|
||||
// width: bl4 + (frame.width * 2),
|
||||
// height: bl4 + (frame.height * 2))
|
||||
|
||||
let ciContext = CIContext(options: nil)
|
||||
guard let ci: CIImage = filter.value(forKey: kCIOutputImageKey) as? CIImage else { return nil }
|
||||
guard let cImg = ciContext.createCGImage(ci, from: contextFrame) else { return nil }
|
||||
|
||||
let finalImage = UIImage(cgImage: cImg)
|
||||
|
||||
return finalImage
|
||||
}
|
||||
|
||||
let p2: CGFloat = 1
|
||||
let p4: CGFloat = p2 * 2
|
||||
|
||||
let bl2 = ((blur * p2) * -1)
|
||||
let bl4 = (blur * p4)
|
||||
|
||||
let x: CGFloat = 0
|
||||
let ofW = (x * 1) * -1
|
||||
let ofH = (x * 1) * -1
|
||||
|
||||
let ofW2 = (x * 2)
|
||||
let ofH2 = (x * 2)
|
||||
|
||||
let contextFrame = CGRect(x: bl2 + ofW,
|
||||
y: bl2 + ofH,
|
||||
width: bl4 + ofW2 + frame.width,
|
||||
height: bl4 + ofH2 + frame.height)
|
||||
|
||||
let blurImageView = UIImageView(frame: contextFrame)
|
||||
blurImageView.image = blurImage(image: screen)
|
||||
blurImageView.contentMode = .scaleAspectFill
|
||||
|
||||
|
||||
addSubview(blurImageView)
|
||||
}
|
||||
|
||||
//MARK: - Rectangle Bezier
|
||||
|
||||
func rectangle(rect: CGRect, radius: CGFloat) -> UIBezierPath {
|
||||
|
||||
let r = self.cornerRadius(radius)
|
||||
let w = rect.width
|
||||
let h = rect.height
|
||||
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: r, y: 0.0))
|
||||
|
||||
path.addLine(to: CGPoint(x: w - r, y: 0.0))
|
||||
path.addArc(withCenter: CGPoint(x: w - r, y: r),
|
||||
radius: r,
|
||||
startAngle: 3.0 * .pi / 2.0,
|
||||
endAngle: 2 * .pi,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: w, y: h - r))
|
||||
path.addArc(withCenter: CGPoint(x: w - r, y: h - r),
|
||||
radius: r,
|
||||
startAngle: 0.0,
|
||||
endAngle: .pi / 2.0,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: r, y: h))
|
||||
path.addArc(withCenter: CGPoint(x: r, y: h - r),
|
||||
radius: r,
|
||||
startAngle: .pi / 2.0,
|
||||
endAngle: .pi,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: 0.0, y: r))
|
||||
path.addArc(withCenter: CGPoint(x: r, y: r),
|
||||
radius: r,
|
||||
startAngle: .pi,
|
||||
endAngle: 3.0 * .pi / 2.0,
|
||||
clockwise: true)
|
||||
|
||||
path.close()
|
||||
return path
|
||||
}
|
||||
|
||||
//MARK: - Star Bezier
|
||||
|
||||
func star(polygon: Bool = false, rect: CGRect, radius: CGFloat, pointsOnStar: Int) -> UIBezierPath {
|
||||
|
||||
let path = UIBezierPath()
|
||||
|
||||
let minSize = max(rect.width, rect.height)
|
||||
var starExtrusion: CGFloat = minSize * 0.18695652173
|
||||
if radius != 0 {
|
||||
starExtrusion = minSize * radius
|
||||
}
|
||||
|
||||
let center: CGPoint = .zero
|
||||
|
||||
var angle: CGFloat = -CGFloat(.pi / 2.0)
|
||||
let angleIncrement = CGFloat(.pi * 2.0 / Double(pointsOnStar))
|
||||
let radius = minSize / 2.0
|
||||
|
||||
var firstPoint = true
|
||||
|
||||
func pointFrom(angle: CGFloat, radius: CGFloat, offset: CGPoint) -> CGPoint {
|
||||
return CGPoint(x: radius * cos(angle) + offset.x,
|
||||
y: radius * sin(angle) + offset.y)
|
||||
}
|
||||
|
||||
for _ in 1...pointsOnStar {
|
||||
|
||||
let point: CGPoint = pointFrom(angle: angle, radius: radius, offset: center)
|
||||
let nextPoint: CGPoint = pointFrom(angle: angle + angleIncrement, radius: radius, offset: center)
|
||||
let midPoint: CGPoint = pointFrom(angle: angle + angleIncrement / 2.0, radius: starExtrusion, offset: center)
|
||||
|
||||
var perY: CGFloat = 1.0
|
||||
var perX: CGFloat = 1.0
|
||||
|
||||
if rect.height < rect.width {
|
||||
perY = (rect.height / minSize)
|
||||
} else {
|
||||
perX = (rect.width / minSize)
|
||||
}
|
||||
|
||||
let p = CGPoint(x: (point.x * perX) + (rect.width / 2.0),
|
||||
y: (point.y * perY) + (rect.height / 2.0))
|
||||
|
||||
let n = CGPoint(x: (nextPoint.x * perX) + (rect.width / 2.0),
|
||||
y: (nextPoint.y * perY) + (rect.height / 2.0))
|
||||
|
||||
let m = CGPoint(x: (midPoint.x * perX) + (rect.width / 2.0),
|
||||
y: (midPoint.y * perY) + (rect.height / 2.0))
|
||||
|
||||
if firstPoint {
|
||||
firstPoint = false
|
||||
path.move(to: p)
|
||||
}
|
||||
|
||||
if !polygon {
|
||||
path.addLine(to: m)
|
||||
}
|
||||
path.addLine(to: n)
|
||||
|
||||
angle += angleIncrement
|
||||
}
|
||||
|
||||
path.close()
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,716 @@
|
||||
//
|
||||
// DesignTest.swift
|
||||
// FigmaConvertXib
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 28.07.2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
@IBDesignable
|
||||
class DesignFigure: UIView {
|
||||
/// Rectange | Ellipse | Polygon | Star
|
||||
@IBInspectable var figureType: Int = 0
|
||||
@IBInspectable var starRadius: CGFloat = 0
|
||||
@IBInspectable var starCount: Int = 5
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
@IBInspectable var blur: CGFloat = 0.0
|
||||
@IBInspectable var image: UIImage?
|
||||
/// ScaleToFill | Fit | Fill
|
||||
@IBInspectable var imageMode: Int = 1
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
@IBInspectable var grColor1: UIColor?
|
||||
@IBInspectable var grColor2: UIColor?
|
||||
@IBInspectable var grColor3: UIColor?
|
||||
@IBInspectable var grColor4: UIColor?
|
||||
@IBInspectable var grColor5: UIColor?
|
||||
@IBInspectable var grColor6: UIColor?
|
||||
@IBInspectable var grStartPoint: CGPoint = .zero
|
||||
@IBInspectable var grEndPoint: CGPoint = .zero
|
||||
/// Default: Linear
|
||||
@IBInspectable var grRadial: Bool = false
|
||||
@IBInspectable var grDrawsOptions: Bool = true
|
||||
@IBInspectable var grDebug: Bool = false
|
||||
@IBInspectable var grPointPercent: Bool = true
|
||||
@IBInspectable var grBlendMode: Int = 0
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
@IBInspectable var brDash: Int = 0
|
||||
@IBInspectable var inShColor: UIColor = .clear
|
||||
@IBInspectable var inShRadius: CGFloat = 0.0
|
||||
@IBInspectable var inShOffset: CGSize = .zero
|
||||
@IBInspectable var shColor: UIColor = .clear
|
||||
@IBInspectable var shRadius: CGFloat = 0.0
|
||||
@IBInspectable var shOffset: CGSize = .zero
|
||||
|
||||
func setup() {
|
||||
|
||||
if let foundView = viewWithTag(55) {
|
||||
foundView.removeFromSuperview()
|
||||
}
|
||||
|
||||
let d = DesignFigure_(frame: bounds)
|
||||
d.backgroundColor = .clear
|
||||
d.tag = 55
|
||||
d.figureType = figureType
|
||||
d.starRadius = starRadius
|
||||
d.starCount = starCount
|
||||
d.cornerRadius = cornerRadius
|
||||
d.blur = blur
|
||||
d.image = image
|
||||
d.imageMode = imageMode
|
||||
d.fillColor = fillColor
|
||||
d.grColor1 = grColor1
|
||||
d.grColor2 = grColor2
|
||||
d.grColor3 = grColor3
|
||||
d.grColor4 = grColor4
|
||||
d.grColor5 = grColor5
|
||||
d.grColor6 = grColor6
|
||||
d.grStartPoint = grStartPoint
|
||||
d.grEndPoint = grEndPoint
|
||||
d.grRadial = grRadial
|
||||
d.grDrawsOptions = grDrawsOptions
|
||||
d.grDebug = grDebug
|
||||
d.grPointPercent = grPointPercent
|
||||
d.grBlendMode = grBlendMode
|
||||
d.brWidth = brWidth
|
||||
d.brColor = brColor
|
||||
d.brDash = brDash
|
||||
d.inShColor = inShColor
|
||||
d.inShRadius = inShRadius
|
||||
d.inShOffset = inShOffset
|
||||
d.shColor = shColor
|
||||
d.shRadius = shRadius
|
||||
d.shOffset = shOffset
|
||||
insertSubview(d, at: 0)
|
||||
|
||||
if grDebug {
|
||||
if let foundView = viewWithTag(44) {
|
||||
foundView.removeFromSuperview()
|
||||
}
|
||||
|
||||
let d = DesignFigureDebug(frame: bounds)
|
||||
d.backgroundColor = .clear
|
||||
d.tag = 44
|
||||
d.grStartPoint = grStartPoint
|
||||
d.grEndPoint = grEndPoint
|
||||
d.grPointPercent = grPointPercent
|
||||
|
||||
insertSubview(d, at: 1)
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() { setup() }
|
||||
|
||||
}
|
||||
|
||||
@IBDesignable
|
||||
class DesignFigureDebug: UIView {
|
||||
|
||||
@IBInspectable var grStartPoint: CGPoint = .zero
|
||||
@IBInspectable var grEndPoint: CGPoint = .zero
|
||||
@IBInspectable var grPointPercent: Bool = true
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
|
||||
addGradientDebugPoints()
|
||||
}
|
||||
|
||||
func addGradientDebugPoints() {
|
||||
|
||||
var start = grStartPoint
|
||||
var end = grStartPoint
|
||||
|
||||
if grPointPercent {
|
||||
end = CGPoint(x: grEndPoint.x * frame.width, y: grEndPoint.y * frame.height)
|
||||
start = CGPoint(x: grStartPoint.x * frame.width, y: grStartPoint.y * frame.height)
|
||||
}
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
let radius: CGFloat = 5
|
||||
|
||||
context.setBlendMode(CGBlendMode.normal)
|
||||
context.setLineWidth(3.0)
|
||||
|
||||
|
||||
context.setFillColor(UIColor.black.cgColor)
|
||||
|
||||
context.setStrokeColor(UIColor.systemRed.cgColor)
|
||||
|
||||
let circleRect = CGRect(x: start.x - radius,
|
||||
y: start.y - radius,
|
||||
width: radius * 2,
|
||||
height: radius * 2)
|
||||
|
||||
context.fillEllipse(in: circleRect)
|
||||
context.strokeEllipse(in: circleRect)
|
||||
|
||||
context.setStrokeColor(UIColor.green.cgColor)
|
||||
|
||||
let circleRect2 = CGRect(x: end.x - radius,
|
||||
y: end.y - radius,
|
||||
width: radius * 2,
|
||||
height: radius * 2)
|
||||
|
||||
context.fillEllipse(in: circleRect2)
|
||||
context.strokeEllipse(in: circleRect2)
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum DesignFigureType: Int {
|
||||
case rectange = 0
|
||||
case ellipse = 1
|
||||
case polygon = 2
|
||||
case star = 3
|
||||
}
|
||||
|
||||
@IBDesignable
|
||||
class DesignFigure_: UIView {
|
||||
|
||||
@IBInspectable var figureType: Int = 0 /// Rectange Ellipse Polygon Star
|
||||
|
||||
@IBInspectable var starRadius: CGFloat = 0
|
||||
@IBInspectable var starCount: Int = 5
|
||||
//MARK: Radius
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
//MARK: Blur
|
||||
|
||||
@IBInspectable var blur: CGFloat = 0.0
|
||||
|
||||
//MARK: Image
|
||||
|
||||
@IBInspectable var image: UIImage?
|
||||
@IBInspectable var imageMode: Int = 1 /// Scale to fill / Aspect fit / fill
|
||||
|
||||
//MARK: Fill
|
||||
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
|
||||
//MARK: Gradient
|
||||
|
||||
@IBInspectable var grColor1: UIColor?
|
||||
@IBInspectable var grColor2: UIColor?
|
||||
@IBInspectable var grColor3: UIColor?
|
||||
@IBInspectable var grColor4: UIColor?
|
||||
@IBInspectable var grColor5: UIColor?
|
||||
@IBInspectable var grColor6: UIColor?
|
||||
|
||||
@IBInspectable var grStartPoint: CGPoint = .zero
|
||||
@IBInspectable var grEndPoint: CGPoint = .zero
|
||||
|
||||
@IBInspectable var grRadial: Bool = false /// default: linear
|
||||
@IBInspectable var grDrawsOptions: Bool = true
|
||||
@IBInspectable var grDebug: Bool = false
|
||||
@IBInspectable var grPointPercent: Bool = true
|
||||
@IBInspectable var grBlendMode: Int = 0
|
||||
|
||||
//MARK: Border
|
||||
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
@IBInspectable var brDash: Int = 0
|
||||
|
||||
//MARK: Inner Shadow
|
||||
|
||||
@IBInspectable var inShColor: UIColor = .clear
|
||||
@IBInspectable var inShRadius: CGFloat = 0.0
|
||||
@IBInspectable var inShOffset: CGSize = .zero
|
||||
|
||||
//MARK: Shadow
|
||||
|
||||
@IBInspectable var shColor: UIColor = .clear
|
||||
@IBInspectable var shRadius: CGFloat = 0.0
|
||||
@IBInspectable var shOffset: CGSize = .zero
|
||||
|
||||
|
||||
|
||||
//MARK: - Draw
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
|
||||
guard let bezier = figurePath(bounds) else { return }
|
||||
|
||||
addFill(bezier: bezier)
|
||||
|
||||
addImage(bezier: bezier)
|
||||
|
||||
addGradient(bezier: bezier)
|
||||
|
||||
addInnerShadow(bezier: bezier)
|
||||
addBorder()
|
||||
addShadow()
|
||||
|
||||
addBlur()
|
||||
}
|
||||
|
||||
//MARK: - Figure Type
|
||||
|
||||
private func figurePath(_ rect: CGRect) -> UIBezierPath? {
|
||||
|
||||
let starR = starRadius / 50
|
||||
if starCount < 1 { starCount = 1 }
|
||||
|
||||
switch figureType {
|
||||
case 1: return UIBezierPath(ovalIn: rect)
|
||||
case 2: return star(polygon: true, rect: rect, radius: starR, pointsOnStar: starCount)
|
||||
case 3: return star(rect: rect, radius: starR, pointsOnStar: starCount)
|
||||
default: return rectangle(rect: rect, radius: cornerRadius)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Border / Stroke
|
||||
|
||||
private func addBorder() {
|
||||
|
||||
if brWidth < 0 { brWidth = 0 }
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
// brColor.setStroke()
|
||||
|
||||
guard let bezier = figurePath(bounds) else { return }
|
||||
|
||||
context.saveGState()
|
||||
|
||||
if brDash > 0 {
|
||||
|
||||
UIColor.clear.setFill()
|
||||
bezier.fill()
|
||||
|
||||
brColor.setStroke()
|
||||
bezier.lineWidth = brWidth * 2
|
||||
|
||||
let dashPattern : [CGFloat] = [ CGFloat(brDash), CGFloat(brDash)]
|
||||
bezier.setLineDash(dashPattern, count: 2, phase: 0)
|
||||
bezier.stroke()
|
||||
|
||||
} else {
|
||||
|
||||
brColor.setStroke()
|
||||
|
||||
bezier.lineWidth = brWidth * 2
|
||||
bezier.stroke()
|
||||
}
|
||||
|
||||
|
||||
|
||||
bezier.stroke()
|
||||
bezier.stroke()
|
||||
|
||||
context.restoreGState()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
|
||||
private func addImage(bezier: UIBezierPath) {
|
||||
|
||||
guard let image = image else { return }
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
var f: CGRect = CGRect(x: 0, y: -bounds.height, width: bounds.width, height: bounds.height)
|
||||
|
||||
if imageMode == 1 || imageMode == 2 {
|
||||
|
||||
var w: CGFloat = 0
|
||||
var h: CGFloat = 0
|
||||
|
||||
let percent = bounds.height / image.size.height
|
||||
w = image.size.width * percent
|
||||
h = bounds.height
|
||||
|
||||
if (imageMode == 1) && (w > bounds.width) ||
|
||||
(imageMode == 2) && (bounds.width > w) {
|
||||
|
||||
let percent = bounds.width / image.size.width
|
||||
h = image.size.height * percent
|
||||
w = bounds.width
|
||||
}
|
||||
|
||||
let x = (bounds.width / 2) - (w / 2)
|
||||
let y = (-h + (((bounds.height / 2) - (h / 2)) * (-1)))
|
||||
|
||||
f = CGRect(x: x, y: y, width: w, height: h)
|
||||
}
|
||||
|
||||
context.saveGState()
|
||||
bezier.addClip()
|
||||
context.scaleBy(x: 1, y: -1)
|
||||
context.draw(image.cgImage!, in: f, byTiling: false)
|
||||
context.restoreGState()
|
||||
|
||||
}
|
||||
|
||||
//MARK: - Fill
|
||||
|
||||
private func addFill(bezier: UIBezierPath) {
|
||||
|
||||
guard let fillColor = fillColor else { return }
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
bezier.close()
|
||||
context.saveGState()
|
||||
fillColor.setFill()
|
||||
bezier.fill()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
//MARK: - Gradient
|
||||
|
||||
private func addGradient(bezier: UIBezierPath) {
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
var colors: [CGColor] = []
|
||||
|
||||
if let c = grColor1 { colors.append(c.cgColor) }
|
||||
if let c = grColor2 { colors.append(c.cgColor) }
|
||||
if let c = grColor3 { colors.append(c.cgColor) }
|
||||
if let c = grColor4 { colors.append(c.cgColor) }
|
||||
if let c = grColor5 { colors.append(c.cgColor) }
|
||||
if let c = grColor6 { colors.append(c.cgColor) }
|
||||
|
||||
if colors.count < 2 {
|
||||
colors.append(UIColor.clear.cgColor)
|
||||
colors.append(UIColor.clear.cgColor)
|
||||
} else if colors.count < 1 {
|
||||
if grColor1 != nil {
|
||||
colors.append(UIColor.clear.cgColor)
|
||||
} else {
|
||||
colors.insert(UIColor.clear.cgColor, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
guard let gradient = CGGradient(colorsSpace: nil,
|
||||
colors: colors as CFArray,
|
||||
locations: nil) else { return }
|
||||
|
||||
let bMode = CGBlendMode(rawValue: CGBlendMode.RawValue(grBlendMode)) ?? CGBlendMode.sourceAtop
|
||||
context.setBlendMode(bMode)
|
||||
|
||||
let options: CGGradientDrawingOptions =
|
||||
grDrawsOptions ? [ .drawsBeforeStartLocation, .drawsAfterEndLocation ] : [ ]
|
||||
|
||||
var grEndPointR: CGPoint = grEndPoint
|
||||
var grStartPointR: CGPoint = grStartPoint
|
||||
if grPointPercent {
|
||||
grEndPointR = CGPoint(x: (grEndPoint.x * frame.width), y: (grEndPoint.y * frame.height))
|
||||
grStartPointR = CGPoint(x: (grStartPoint.x * frame.width), y: (grStartPoint.y * frame.height))
|
||||
}
|
||||
|
||||
bezier.close()
|
||||
context.saveGState()
|
||||
bezier.addClip()
|
||||
|
||||
if grRadial {
|
||||
|
||||
let x: CGFloat = (grEndPointR.x - grStartPointR.x)
|
||||
let y: CGFloat = (grEndPointR.y - grStartPointR.y)
|
||||
let distance: CGFloat = sqrt((x * x) + (y * y))
|
||||
|
||||
context.drawRadialGradient(gradient,
|
||||
startCenter: grStartPointR,
|
||||
startRadius: 0,
|
||||
endCenter: grStartPointR,
|
||||
endRadius: distance,
|
||||
options: options)
|
||||
} else {
|
||||
|
||||
context.drawLinearGradient(gradient,
|
||||
start: grStartPointR,
|
||||
end: grEndPointR,
|
||||
options: options)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Shadow
|
||||
|
||||
private func addShadow() {
|
||||
|
||||
if shRadius < 0 { shRadius = 0 }
|
||||
|
||||
layer.shadowOffset = shOffset
|
||||
layer.shadowOpacity = 1.0
|
||||
layer.shadowRadius = shRadius
|
||||
layer.shadowColor = shColor.cgColor
|
||||
}
|
||||
|
||||
private func addShadowContext(bezier: UIBezierPath) {
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
bezier.close()
|
||||
|
||||
//// Shadow Declarations
|
||||
let shadow = NSShadow()
|
||||
shadow.shadowColor = shColor
|
||||
shadow.shadowOffset = shOffset
|
||||
shadow.shadowBlurRadius = shRadius * 2
|
||||
|
||||
context.saveGState()
|
||||
|
||||
context.setShadow(offset: shadow.shadowOffset,
|
||||
blur: shadow.shadowBlurRadius,
|
||||
color: (shadow.shadowColor as! UIColor).cgColor)
|
||||
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
//MARK: - Inner Shadow
|
||||
|
||||
private func addInnerShadow(bezier: UIBezierPath) {
|
||||
|
||||
if inShRadius < 0 { inShRadius = 0 }
|
||||
|
||||
let innerShadow = NSShadow()
|
||||
innerShadow.shadowColor = inShColor
|
||||
innerShadow.shadowOffset = inShOffset
|
||||
innerShadow.shadowBlurRadius = inShRadius * 2
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
////// Star Inner Shadow
|
||||
context.saveGState()
|
||||
context.clip(to: bezier.bounds)
|
||||
context.setShadow(offset: .zero, blur: 0)
|
||||
context.setAlpha((innerShadow.shadowColor as! UIColor).cgColor.alpha)
|
||||
context.beginTransparencyLayer(auxiliaryInfo: nil)
|
||||
|
||||
let starOpaqueShadow = (innerShadow.shadowColor as! UIColor).withAlphaComponent(1)
|
||||
context.setShadow(offset: innerShadow.shadowOffset,
|
||||
blur: innerShadow.shadowBlurRadius,
|
||||
color: starOpaqueShadow.cgColor)
|
||||
|
||||
context.setBlendMode(.sourceOut)
|
||||
context.beginTransparencyLayer(auxiliaryInfo: nil)
|
||||
|
||||
starOpaqueShadow.setFill()
|
||||
|
||||
bezier.fill()
|
||||
|
||||
context.endTransparencyLayer()
|
||||
context.endTransparencyLayer()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Blur
|
||||
|
||||
private func addBlur() {
|
||||
|
||||
guard blur > 0 else { return }
|
||||
|
||||
guard let image = screenShotContext() else { return }
|
||||
|
||||
self.addblur(screen: image, blur: blur)
|
||||
}
|
||||
|
||||
// MARK: Screen Shot Context
|
||||
|
||||
private func screenShotContext() -> UIImage? {
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return nil }
|
||||
guard let cgimage: CGImage = context.makeImage() else { return nil }
|
||||
let image = UIImage(cgImage: cgimage)
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
// MARK: Clear Context
|
||||
|
||||
private func clearContext() {
|
||||
|
||||
let f = CGRect(x: 0, y: 0,
|
||||
width: frame.width * 2,
|
||||
height: frame.height * 2)
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
context?.clear(f)
|
||||
}
|
||||
|
||||
|
||||
private func addblur(screen: UIImage, blur: CGFloat) {
|
||||
|
||||
clearContext()
|
||||
|
||||
func blurImage(image: UIImage) -> UIImage? {
|
||||
|
||||
guard let ciscreen: CIImage = CIImage(image: screen) else { return nil }
|
||||
|
||||
guard let filter: CIFilter = CIFilter(name: "CIGaussianBlur") else { return nil }
|
||||
filter.setDefaults()
|
||||
filter.setValue(ciscreen, forKey: kCIInputImageKey)
|
||||
filter.setValue(blur, forKey: kCIInputRadiusKey)
|
||||
|
||||
|
||||
let bl2 = ((blur * 3) * -1)
|
||||
let bl4 = (blur * 6)
|
||||
|
||||
let contextFrame = CGRect(
|
||||
x: bl2,
|
||||
y: bl2,
|
||||
width: bl4 + image.size.width,
|
||||
height: bl4 + image.size.height
|
||||
)
|
||||
|
||||
// let bl2 = ((blur * 2) * -1)
|
||||
// let bl4 = (blur * 4)
|
||||
// let contextFrame2 = CGRect(x: bl2,
|
||||
// y: bl2,
|
||||
// width: bl4 + (frame.width * 2),
|
||||
// height: bl4 + (frame.height * 2))
|
||||
|
||||
let ciContext = CIContext(options: nil)
|
||||
guard let ci: CIImage = filter.value(forKey: kCIOutputImageKey) as? CIImage else { return nil }
|
||||
guard let cImg = ciContext.createCGImage(ci, from: contextFrame) else { return nil }
|
||||
|
||||
let finalImage = UIImage(cgImage: cImg)
|
||||
|
||||
return finalImage
|
||||
}
|
||||
|
||||
let p2: CGFloat = 1
|
||||
let p4: CGFloat = p2 * 2
|
||||
|
||||
let bl2 = ((blur * p2) * -1)
|
||||
let bl4 = (blur * p4)
|
||||
|
||||
let x: CGFloat = 0
|
||||
let ofW = (x * 1) * -1
|
||||
let ofH = (x * 1) * -1
|
||||
|
||||
let ofW2 = (x * 2)
|
||||
let ofH2 = (x * 2)
|
||||
|
||||
let contextFrame = CGRect(x: bl2 + ofW,
|
||||
y: bl2 + ofH,
|
||||
width: bl4 + ofW2 + frame.width,
|
||||
height: bl4 + ofH2 + frame.height)
|
||||
|
||||
let blurImageView = UIImageView(frame: contextFrame)
|
||||
blurImageView.image = blurImage(image: screen)
|
||||
blurImageView.contentMode = .scaleAspectFill
|
||||
|
||||
|
||||
addSubview(blurImageView)
|
||||
}
|
||||
|
||||
//MARK: - Rectangle Bezier
|
||||
|
||||
func rectangle(rect: CGRect, radius: CGFloat) -> UIBezierPath {
|
||||
|
||||
let r = self.cornerRadius(radius)
|
||||
let w = rect.width
|
||||
let h = rect.height
|
||||
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: r, y: 0.0))
|
||||
|
||||
path.addLine(to: CGPoint(x: w - r, y: 0.0))
|
||||
path.addArc(withCenter: CGPoint(x: w - r, y: r),
|
||||
radius: r,
|
||||
startAngle: 3.0 * .pi / 2.0,
|
||||
endAngle: 2 * .pi,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: w, y: h - r))
|
||||
path.addArc(withCenter: CGPoint(x: w - r, y: h - r),
|
||||
radius: r,
|
||||
startAngle: 0.0,
|
||||
endAngle: .pi / 2.0,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: r, y: h))
|
||||
path.addArc(withCenter: CGPoint(x: r, y: h - r),
|
||||
radius: r,
|
||||
startAngle: .pi / 2.0,
|
||||
endAngle: .pi,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: 0.0, y: r))
|
||||
path.addArc(withCenter: CGPoint(x: r, y: r),
|
||||
radius: r,
|
||||
startAngle: .pi,
|
||||
endAngle: 3.0 * .pi / 2.0,
|
||||
clockwise: true)
|
||||
|
||||
path.close()
|
||||
return path
|
||||
}
|
||||
|
||||
//MARK: - Star Bezier
|
||||
|
||||
func star(polygon: Bool = false, rect: CGRect, radius: CGFloat, pointsOnStar: Int) -> UIBezierPath {
|
||||
|
||||
let path = UIBezierPath()
|
||||
|
||||
let minSize = max(rect.width, rect.height)
|
||||
var starExtrusion: CGFloat = minSize * 0.18695652173
|
||||
if radius != 0 {
|
||||
starExtrusion = minSize * radius
|
||||
}
|
||||
|
||||
let center: CGPoint = .zero
|
||||
|
||||
var angle: CGFloat = -CGFloat(.pi / 2.0)
|
||||
let angleIncrement = CGFloat(.pi * 2.0 / Double(pointsOnStar))
|
||||
let radius = minSize / 2.0
|
||||
|
||||
var firstPoint = true
|
||||
|
||||
func pointFrom(angle: CGFloat, radius: CGFloat, offset: CGPoint) -> CGPoint {
|
||||
return CGPoint(x: radius * cos(angle) + offset.x,
|
||||
y: radius * sin(angle) + offset.y)
|
||||
}
|
||||
|
||||
for _ in 1...pointsOnStar {
|
||||
|
||||
let point: CGPoint = pointFrom(angle: angle, radius: radius, offset: center)
|
||||
let nextPoint: CGPoint = pointFrom(angle: angle + angleIncrement, radius: radius, offset: center)
|
||||
let midPoint: CGPoint = pointFrom(angle: angle + angleIncrement / 2.0, radius: starExtrusion, offset: center)
|
||||
|
||||
var perY: CGFloat = 1.0
|
||||
var perX: CGFloat = 1.0
|
||||
|
||||
if rect.height < rect.width {
|
||||
perY = (rect.height / minSize)
|
||||
} else {
|
||||
perX = (rect.width / minSize)
|
||||
}
|
||||
|
||||
let p = CGPoint(x: (point.x * perX) + (rect.width / 2.0),
|
||||
y: (point.y * perY) + (rect.height / 2.0))
|
||||
|
||||
let n = CGPoint(x: (nextPoint.x * perX) + (rect.width / 2.0),
|
||||
y: (nextPoint.y * perY) + (rect.height / 2.0))
|
||||
|
||||
let m = CGPoint(x: (midPoint.x * perX) + (rect.width / 2.0),
|
||||
y: (midPoint.y * perY) + (rect.height / 2.0))
|
||||
|
||||
if firstPoint {
|
||||
firstPoint = false
|
||||
path.move(to: p)
|
||||
}
|
||||
|
||||
if !polygon {
|
||||
path.addLine(to: m)
|
||||
}
|
||||
path.addLine(to: n)
|
||||
|
||||
angle += angleIncrement
|
||||
}
|
||||
|
||||
path.close()
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
//
|
||||
// DesignTest.swift
|
||||
// FigmaConvertXib
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 28.07.2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@IBDesignable
|
||||
class DesignFillView_: UIView {
|
||||
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
//MARK: Blur
|
||||
|
||||
@IBInspectable var blur: CGFloat = 0.0
|
||||
|
||||
//MARK: Image
|
||||
|
||||
@IBInspectable var image: UIImage?
|
||||
@IBInspectable var imageMode: Int = 1 /// Scale to fill / Aspect fit / fill
|
||||
|
||||
//MARK: Fill
|
||||
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
//MARK: Border
|
||||
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
@IBInspectable var brDash: Int = 0
|
||||
|
||||
|
||||
|
||||
@IBInspectable var shColor: UIColor = .clear
|
||||
@IBInspectable var shRadius: CGFloat = 0.0
|
||||
@IBInspectable var shOffset: CGSize = .zero
|
||||
|
||||
//MARK: - Draw
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
|
||||
guard let bezier = figurePath(bounds) else { return }
|
||||
|
||||
addFill(bezier: bezier)
|
||||
addImage(bezier: bezier)
|
||||
addBorder()
|
||||
addShadow()
|
||||
addBlur()
|
||||
}
|
||||
|
||||
//MARK: - Figure Type
|
||||
|
||||
private func figurePath(_ rect: CGRect) -> UIBezierPath? {
|
||||
return rectangle(rect: rect, radius: cornerRadius)
|
||||
}
|
||||
|
||||
|
||||
|
||||
private func addImage(bezier: UIBezierPath) {
|
||||
|
||||
guard let image = image else { return }
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
var f: CGRect = CGRect(x: 0, y: -bounds.height, width: bounds.width, height: bounds.height)
|
||||
|
||||
if imageMode == 1 || imageMode == 2 {
|
||||
|
||||
var w: CGFloat = 0
|
||||
var h: CGFloat = 0
|
||||
|
||||
let percent = bounds.height / image.size.height
|
||||
w = image.size.width * percent
|
||||
h = bounds.height
|
||||
|
||||
if (imageMode == 1) && (w > bounds.width) ||
|
||||
(imageMode == 2) && (bounds.width > w) {
|
||||
|
||||
let percent = bounds.width / image.size.width
|
||||
h = image.size.height * percent
|
||||
w = bounds.width
|
||||
}
|
||||
|
||||
let x = (bounds.width / 2) - (w / 2)
|
||||
let y = (-h + (((bounds.height / 2) - (h / 2)) * (-1)))
|
||||
|
||||
f = CGRect(x: x, y: y, width: w, height: h)
|
||||
}
|
||||
|
||||
context.saveGState()
|
||||
bezier.addClip()
|
||||
context.scaleBy(x: 1, y: -1)
|
||||
context.draw(image.cgImage!, in: f, byTiling: false)
|
||||
context.restoreGState()
|
||||
|
||||
}
|
||||
|
||||
//MARK: - Fill
|
||||
|
||||
private func addFill(bezier: UIBezierPath) {
|
||||
|
||||
guard let fillColor = fillColor else { return }
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
bezier.close()
|
||||
context.saveGState()
|
||||
fillColor.setFill()
|
||||
bezier.fill()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
|
||||
//MARK: - Shadow
|
||||
|
||||
private func addShadow() {
|
||||
|
||||
if shRadius < 0 { shRadius = 0 }
|
||||
|
||||
layer.shadowOffset = shOffset
|
||||
layer.shadowOpacity = 1.0
|
||||
layer.shadowRadius = shRadius
|
||||
layer.shadowColor = shColor.cgColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Border / Stroke
|
||||
|
||||
private func addBorder() {
|
||||
|
||||
if brWidth < 0 { brWidth = 0 }
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
// brColor.setStroke()
|
||||
|
||||
guard let bezier = figurePath(bounds) else { return }
|
||||
|
||||
context.saveGState()
|
||||
|
||||
if brDash > 0 {
|
||||
|
||||
UIColor.clear.setFill()
|
||||
bezier.fill()
|
||||
|
||||
brColor.setStroke()
|
||||
bezier.lineWidth = brWidth * 2
|
||||
|
||||
let dashPattern : [CGFloat] = [ CGFloat(brDash), CGFloat(brDash)]
|
||||
bezier.setLineDash(dashPattern, count: 2, phase: 0)
|
||||
bezier.stroke()
|
||||
|
||||
} else {
|
||||
|
||||
brColor.setStroke()
|
||||
|
||||
bezier.lineWidth = brWidth * 2
|
||||
bezier.stroke()
|
||||
}
|
||||
|
||||
|
||||
|
||||
bezier.stroke()
|
||||
bezier.stroke()
|
||||
|
||||
context.restoreGState()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Blur
|
||||
|
||||
private func addBlur() {
|
||||
|
||||
guard blur > 0 else { return }
|
||||
|
||||
guard let image = screenShotContext() else { return }
|
||||
|
||||
self.addblur(screen: image, blur: blur)
|
||||
}
|
||||
|
||||
// MARK: Screen Shot Context
|
||||
|
||||
private func screenShotContext() -> UIImage? {
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return nil }
|
||||
guard let cgimage: CGImage = context.makeImage() else { return nil }
|
||||
let image = UIImage(cgImage: cgimage)
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
// MARK: Clear Context
|
||||
|
||||
private func clearContext() {
|
||||
|
||||
let f = CGRect(x: 0, y: 0,
|
||||
width: frame.width * 2,
|
||||
height: frame.height * 2)
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
context?.clear(f)
|
||||
}
|
||||
|
||||
|
||||
private func addblur(screen: UIImage, blur: CGFloat) {
|
||||
|
||||
clearContext()
|
||||
|
||||
func blurImage(image: UIImage) -> UIImage? {
|
||||
|
||||
guard let ciscreen: CIImage = CIImage(image: screen) else { return nil }
|
||||
|
||||
guard let filter: CIFilter = CIFilter(name: "CIGaussianBlur") else { return nil }
|
||||
filter.setDefaults()
|
||||
filter.setValue(ciscreen, forKey: kCIInputImageKey)
|
||||
filter.setValue(blur, forKey: kCIInputRadiusKey)
|
||||
|
||||
|
||||
let bl2 = ((blur * 3) * -1)
|
||||
let bl4 = (blur * 6)
|
||||
|
||||
let contextFrame = CGRect(
|
||||
x: bl2,
|
||||
y: bl2,
|
||||
width: bl4 + image.size.width,
|
||||
height: bl4 + image.size.height
|
||||
)
|
||||
|
||||
// let bl2 = ((blur * 2) * -1)
|
||||
// let bl4 = (blur * 4)
|
||||
// let contextFrame2 = CGRect(x: bl2,
|
||||
// y: bl2,
|
||||
// width: bl4 + (frame.width * 2),
|
||||
// height: bl4 + (frame.height * 2))
|
||||
|
||||
let ciContext = CIContext(options: nil)
|
||||
guard let ci: CIImage = filter.value(forKey: kCIOutputImageKey) as? CIImage else { return nil }
|
||||
guard let cImg = ciContext.createCGImage(ci, from: contextFrame) else { return nil }
|
||||
|
||||
let finalImage = UIImage(cgImage: cImg)
|
||||
|
||||
return finalImage
|
||||
}
|
||||
|
||||
let p2: CGFloat = 1
|
||||
let p4: CGFloat = p2 * 2
|
||||
|
||||
let bl2 = ((blur * p2) * -1)
|
||||
let bl4 = (blur * p4)
|
||||
|
||||
let x: CGFloat = 0
|
||||
let ofW = (x * 1) * -1
|
||||
let ofH = (x * 1) * -1
|
||||
|
||||
let ofW2 = (x * 2)
|
||||
let ofH2 = (x * 2)
|
||||
|
||||
let contextFrame = CGRect(x: bl2 + ofW,
|
||||
y: bl2 + ofH,
|
||||
width: bl4 + ofW2 + frame.width,
|
||||
height: bl4 + ofH2 + frame.height)
|
||||
|
||||
let blurImageView = UIImageView(frame: contextFrame)
|
||||
blurImageView.image = blurImage(image: screen)
|
||||
blurImageView.contentMode = .scaleAspectFill
|
||||
|
||||
|
||||
addSubview(blurImageView)
|
||||
}
|
||||
|
||||
//MARK: - Rectangle Bezier
|
||||
|
||||
func rectangle(rect: CGRect, radius: CGFloat) -> UIBezierPath {
|
||||
|
||||
let r = self.cornerRadius(radius)
|
||||
let w = rect.width
|
||||
let h = rect.height
|
||||
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: r, y: 0.0))
|
||||
|
||||
path.addLine(to: CGPoint(x: w - r, y: 0.0))
|
||||
path.addArc(withCenter: CGPoint(x: w - r, y: r),
|
||||
radius: r,
|
||||
startAngle: 3.0 * .pi / 2.0,
|
||||
endAngle: 2 * .pi,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: w, y: h - r))
|
||||
path.addArc(withCenter: CGPoint(x: w - r, y: h - r),
|
||||
radius: r,
|
||||
startAngle: 0.0,
|
||||
endAngle: .pi / 2.0,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: r, y: h))
|
||||
path.addArc(withCenter: CGPoint(x: r, y: h - r),
|
||||
radius: r,
|
||||
startAngle: .pi / 2.0,
|
||||
endAngle: .pi,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: 0.0, y: r))
|
||||
path.addArc(withCenter: CGPoint(x: r, y: r),
|
||||
radius: r,
|
||||
startAngle: .pi,
|
||||
endAngle: 3.0 * .pi / 2.0,
|
||||
clockwise: true)
|
||||
|
||||
path.close()
|
||||
return path
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
//
|
||||
// DesignImageView.swift
|
||||
// FigmaConvertXib
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 21.07.2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class DesignImage: UIImageView {
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
/// Shadow
|
||||
@IBInspectable var shColor: UIColor = .clear
|
||||
@IBInspectable var shRadius: CGFloat = 0.0
|
||||
@IBInspectable var shOffset: CGSize = .zero
|
||||
|
||||
/// Border
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
|
||||
super.draw(rect)
|
||||
|
||||
layer.backgroundColor = UIColor.black.cgColor
|
||||
|
||||
let l = CALayer()
|
||||
l.frame = rect
|
||||
l.borderColor = brColor.cgColor
|
||||
l.borderWidth = brWidth
|
||||
l.cornerRadius = cornerRadius
|
||||
layer.addSublayer(l)
|
||||
|
||||
add(shadow: shRadius, offset: shOffset, color: shColor)
|
||||
add(border: brWidth, color: brColor)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@IBDesignable
|
||||
class DesignImageView: UIView {
|
||||
|
||||
//MARK: Properties
|
||||
|
||||
@IBInspectable var image: UIImage?
|
||||
@IBInspectable var contModeFill: Bool = false
|
||||
|
||||
/// Radius
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
/// Blur
|
||||
@IBInspectable var blur: CGFloat = 0.0
|
||||
|
||||
/// Fill
|
||||
@IBInspectable var fillColor: UIColor = .clear
|
||||
|
||||
/// Gradient
|
||||
@IBInspectable var grColor2: UIColor?
|
||||
@IBInspectable var grColor3: UIColor?
|
||||
@IBInspectable var grColor4: UIColor?
|
||||
@IBInspectable var grColor5: UIColor?
|
||||
@IBInspectable var grColor6: UIColor?
|
||||
|
||||
@IBInspectable var grStartPoint: CGPoint = .zero
|
||||
@IBInspectable var grEndPoint: CGPoint = .zero
|
||||
|
||||
/// Inner Shadow
|
||||
@IBInspectable var inShColor: UIColor = .clear
|
||||
@IBInspectable var inShRadius: CGFloat = 0.0
|
||||
@IBInspectable var inShOffset: CGSize = .zero
|
||||
|
||||
/// Shadow
|
||||
@IBInspectable var shColor: UIColor = .clear
|
||||
@IBInspectable var shRadius: CGFloat = 0.0
|
||||
@IBInspectable var shOffset: CGSize = .zero
|
||||
|
||||
/// Border
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
|
||||
var imageView: UIImageView!
|
||||
|
||||
|
||||
//MARK: - Draw
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
|
||||
let radius = self.cornerRadius(cornerRadius)
|
||||
|
||||
layer.cornerRadius = radius
|
||||
|
||||
layer.backgroundColor = fillColor.cgColor
|
||||
|
||||
addShadow()
|
||||
addBorder()
|
||||
|
||||
addImage()
|
||||
|
||||
addGradient()
|
||||
addInnerShadow()
|
||||
|
||||
add(blur: blur) { [weak self] in
|
||||
|
||||
guard let _self = self else { return }
|
||||
_self.imageView.setAlpha(.invisible)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func addGradient() {
|
||||
|
||||
guard let glayer = add(gradient: fillColor,
|
||||
color2: grColor2,
|
||||
color3: grColor3,
|
||||
color4: grColor4,
|
||||
color5: grColor5,
|
||||
color6: grColor6,
|
||||
pointStart: grStartPoint,
|
||||
pointEnd: grEndPoint,
|
||||
cornerRadius: cornerRadius) else { return }
|
||||
|
||||
imageView.layer.insertSublayer(glayer, at: 0)
|
||||
|
||||
}
|
||||
|
||||
private func addInnerShadow() {
|
||||
|
||||
addInnerShadow(color: inShColor,
|
||||
radius: inShRadius,
|
||||
offset: inShOffset,
|
||||
cornerRadius: cornerRadius)
|
||||
|
||||
}
|
||||
|
||||
private func addShadow() {
|
||||
layer.shadowOffset = shOffset
|
||||
layer.shadowOpacity = 1.0
|
||||
layer.shadowRadius = shRadius
|
||||
layer.shadowColor = shColor.cgColor
|
||||
}
|
||||
|
||||
private func addBorder() {
|
||||
layer.borderColor = brColor.cgColor
|
||||
layer.borderWidth = brWidth
|
||||
}
|
||||
|
||||
private func addImage() {
|
||||
let f = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
|
||||
imageView = UIImageView(frame: f)
|
||||
imageView.image = image
|
||||
imageView.contentMode = contModeFill ? .scaleAspectFill : .scaleAspectFit
|
||||
imageView.clipsToBounds = true
|
||||
imageView.layer.cornerRadius = self.cornerRadius(cornerRadius)
|
||||
insertSubview(imageView, at: 0)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// DesignInnerShadow.swift
|
||||
// FigmaConvertXib
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 12.02.2021.
|
||||
// Copyright © 2021 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class DesignInnerShadow: UIView {
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
@IBInspectable var inShColor: UIColor = .clear
|
||||
@IBInspectable var inShRadius: CGFloat = 0.0
|
||||
@IBInspectable var inShOffset: CGSize = .zero
|
||||
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
|
||||
//MARK: - Draw
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
|
||||
addInnerShadow(rect)
|
||||
|
||||
layer.backgroundColor = fillColor?.cgColor
|
||||
layer.cornerRadius = cornerRadius
|
||||
}
|
||||
|
||||
//MARK: - Add Shadow
|
||||
|
||||
func addInnerShadow(_ rect: CGRect) {
|
||||
/// General Declarations
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
|
||||
|
||||
UIColor(white: 1.0, alpha: 0.00001).setFill()
|
||||
|
||||
path.fill()
|
||||
context.saveGState()
|
||||
context.clip(to: path.bounds)
|
||||
context.setAlpha(CGFloat(1.0))
|
||||
context.beginTransparencyLayer(auxiliaryInfo: nil)
|
||||
|
||||
|
||||
let innerShadow = NSShadow()
|
||||
innerShadow.shadowColor = inShColor
|
||||
innerShadow.shadowOffset = inShOffset
|
||||
innerShadow.shadowBlurRadius = inShRadius * 2
|
||||
|
||||
context.setShadow(offset: inShOffset,
|
||||
blur: inShRadius,
|
||||
color: inShColor.cgColor)
|
||||
context.setBlendMode(.sourceOut)
|
||||
context.beginTransparencyLayer(auxiliaryInfo: nil)
|
||||
let ovalOpaqueShadow = UIColor.init(cgColor: inShColor.cgColor)
|
||||
ovalOpaqueShadow.setFill()
|
||||
path.fill()
|
||||
context.endTransparencyLayer()
|
||||
context.endTransparencyLayer()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// DesignLabel.swift
|
||||
// FigmaConvertXib
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 25.07.2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class DesignLabel: UILabel {
|
||||
|
||||
//MARK: Gradient
|
||||
@IBInspectable var grColor1: UIColor?
|
||||
@IBInspectable var grColor2: UIColor?
|
||||
@IBInspectable var grColor3: UIColor?
|
||||
@IBInspectable var grColor4: UIColor?
|
||||
@IBInspectable var grColor5: UIColor?
|
||||
@IBInspectable var grColor6: UIColor?
|
||||
|
||||
@IBInspectable var grStartPoint: CGPoint = .zero
|
||||
@IBInspectable var grEndPoint: CGPoint = .zero
|
||||
|
||||
@IBInspectable var grRadial: Bool = false /// default: linear
|
||||
@IBInspectable var grDrawsOptions: Bool = true
|
||||
@IBInspectable var grDebug: Bool = false
|
||||
@IBInspectable var grPointPercent: Bool = true
|
||||
@IBInspectable var grBlendMode: Int = 20 /// заполнители
|
||||
|
||||
//MARK: - Shadow
|
||||
|
||||
@IBInspectable var shColor: UIColor = .clear
|
||||
@IBInspectable var shRadius: CGFloat = 0.0
|
||||
@IBInspectable var shOffset: CGSize = .zero
|
||||
|
||||
//MARK: Border
|
||||
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
|
||||
//MARK: - Draw
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
shadow()
|
||||
gradient()
|
||||
gradientDebug()
|
||||
border()
|
||||
}
|
||||
|
||||
//MARK: - Add Shadow
|
||||
|
||||
private func shadow() {
|
||||
layer.shadowOpacity = 1.0
|
||||
layer.shadowOffset = shOffset
|
||||
layer.shadowRadius = shRadius
|
||||
layer.shadowColor = shColor.cgColor
|
||||
}
|
||||
|
||||
//MARK: - Add Border
|
||||
|
||||
func border() {
|
||||
|
||||
guard brColor != .clear, brWidth != 0.0 else { return }
|
||||
|
||||
let borderLabel = UILabel(frame: bounds)
|
||||
borderLabel.text = text
|
||||
borderLabel.font = font
|
||||
borderLabel.textColor = .clear
|
||||
borderLabel.numberOfLines = numberOfLines
|
||||
borderLabel.backgroundColor = .clear
|
||||
|
||||
|
||||
let strokeTextAttributes: [NSAttributedString.Key : Any] = [
|
||||
.strokeColor : brColor,
|
||||
.strokeWidth : (-1 * (brWidth * 2)),
|
||||
]
|
||||
|
||||
borderLabel.attributedText = NSAttributedString(string: text ?? "",
|
||||
attributes: strokeTextAttributes)
|
||||
|
||||
addSubview(borderLabel)
|
||||
}
|
||||
|
||||
//MARK: - Add Gradient
|
||||
|
||||
func gradient() {
|
||||
var colors: [CGColor] = []
|
||||
|
||||
if grColor1 != nil || grColor2 != nil || grColor3 != nil || grColor4 != nil || grColor5 != nil {
|
||||
|
||||
if let color1 = grColor1 { colors.append(color1.cgColor) }
|
||||
if let color2 = grColor2 { colors.append(color2.cgColor) }
|
||||
if let color3 = grColor3 { colors.append(color3.cgColor) }
|
||||
if let color4 = grColor4 { colors.append(color4.cgColor) }
|
||||
if let color5 = grColor5 { colors.append(color5.cgColor) }
|
||||
if let color6 = grColor6 { colors.append(color6.cgColor) }
|
||||
|
||||
} else { return }
|
||||
|
||||
guard let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(),
|
||||
colors: colors as CFArray,
|
||||
locations: nil) else { return }
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
let bMode = CGBlendMode(rawValue: CGBlendMode.RawValue(grBlendMode)) ?? CGBlendMode.sourceAtop
|
||||
context.setBlendMode(bMode)
|
||||
|
||||
let options: CGGradientDrawingOptions =
|
||||
grDrawsOptions ? [ .drawsBeforeStartLocation, .drawsAfterEndLocation ] : [ ]
|
||||
|
||||
|
||||
if grPointPercent {
|
||||
grEndPoint = CGPoint(x: grEndPoint.x * frame.width, y: grEndPoint.y * frame.height)
|
||||
grStartPoint = CGPoint(x: grStartPoint.x * frame.width, y: grStartPoint.y * frame.height)
|
||||
}
|
||||
|
||||
//MARK: Gr. Radial / Linear
|
||||
if grRadial {
|
||||
let x: CGFloat = (grEndPoint.x - grStartPoint.x)
|
||||
let y: CGFloat = (grEndPoint.y - grStartPoint.y)
|
||||
let distance: CGFloat = sqrt((x * x) + (y * y))
|
||||
|
||||
context.drawRadialGradient(gradient,
|
||||
startCenter: grStartPoint,
|
||||
startRadius: 0,
|
||||
endCenter: grStartPoint,
|
||||
endRadius: distance,
|
||||
options: options)
|
||||
} else {
|
||||
context.drawLinearGradient(gradient,
|
||||
start: grStartPoint,
|
||||
end: grEndPoint,
|
||||
options: options)
|
||||
}
|
||||
context.saveGState()
|
||||
|
||||
}
|
||||
|
||||
//MARK: - Gr. Debug
|
||||
|
||||
func gradientDebug() {
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
guard grDebug else { return }
|
||||
|
||||
context.setBlendMode(CGBlendMode.normal)
|
||||
context.setLineWidth(3.0)
|
||||
context.setFillColor(UIColor.black.cgColor)
|
||||
context.setStrokeColor(UIColor.systemRed.cgColor)
|
||||
|
||||
let ellipseRadius: CGFloat = 5
|
||||
let circleRect = CGRect(x: grStartPoint.x - ellipseRadius,
|
||||
y: grStartPoint.y - ellipseRadius,
|
||||
width: ellipseRadius * 2,
|
||||
height: ellipseRadius * 2)
|
||||
|
||||
context.fillEllipse(in: circleRect)
|
||||
context.strokeEllipse(in: circleRect)
|
||||
context.setStrokeColor(UIColor.green.cgColor)
|
||||
|
||||
let circleRect2 = CGRect(x: grEndPoint.x - ellipseRadius,
|
||||
y: grEndPoint.y - ellipseRadius,
|
||||
width: ellipseRadius * 2,
|
||||
height: ellipseRadius * 2)
|
||||
|
||||
context.fillEllipse(in: circleRect2)
|
||||
context.strokeEllipse(in: circleRect2)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
//
|
||||
// DesignTest.swift
|
||||
// FigmaConvertXib
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 28.07.2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
private extension UIButton {
|
||||
|
||||
func style(selected: Bool) {
|
||||
if selected {
|
||||
self.setTitleColor(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), for: .normal)
|
||||
} else {
|
||||
self.setTitleColor(#colorLiteral(red: 0.3098039216, green: 0.3098039216, blue: 0.3098039216, alpha: 1), for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension UIView {
|
||||
|
||||
func selectedView() {
|
||||
|
||||
self.layer.cornerRadius = 8
|
||||
self.layer.backgroundColor = #colorLiteral(red: 0.9960784314, green: 0.3137254902, blue: 0, alpha: 1).cgColor
|
||||
|
||||
self.add(shadow: 6 / 2, offset: CGSize(width: -4, height: -4), color: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1))
|
||||
|
||||
self.addInnerShadow(color: #colorLiteral(red: 0.4784313725, green: 0.1960784314, blue: 0.01568627451, alpha: 0.3674445752),
|
||||
radius: 9 / 2,
|
||||
offset: CGSize(width: -4, height: -4),
|
||||
cornerRadius: 8)
|
||||
|
||||
self.addInnerShadow(color: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.3720056395),
|
||||
radius: 8 / 2,
|
||||
offset: CGSize(width: 4, height: 4),
|
||||
cornerRadius: 8)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@IBDesignable
|
||||
class DesignSegmentBar: UIView {
|
||||
|
||||
@IBInspectable var textItems: String = ""
|
||||
@IBInspectable var index: Int = 0
|
||||
|
||||
private var views: [UIView] = []
|
||||
private var buttons: [UIButton] = []
|
||||
private var stackView: UIStackView!
|
||||
private var seView: UIImageView!
|
||||
|
||||
var changeIndex: ((Int) -> ())?
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
|
||||
layer.cornerRadius = 8
|
||||
layer.backgroundColor = #colorLiteral(red: 0.9411764706, green: 0.9411764706, blue: 0.9529411765, alpha: 1).cgColor
|
||||
|
||||
for view in buttons {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
for view in views {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
for view in subviews {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
|
||||
let items = textItems.components(separatedBy: ",")
|
||||
let width = (self.frame.width / CGFloat(items.count))
|
||||
let frame = CGRect(x: (width * CGFloat(index)), y: 0, width: width, height: self.frame.height)
|
||||
|
||||
views = []
|
||||
buttons = []
|
||||
|
||||
for i in 0..<items.count {
|
||||
|
||||
let x = ((width * CGFloat(i+1)) - 0.5)
|
||||
|
||||
if (i+1) != items.count {
|
||||
let separatorView = UIView(frame: CGRect(x: x, y: 11, width: 1, height: self.frame.height - 22))
|
||||
separatorView.backgroundColor = #colorLiteral(red: 0.7411764706, green: 0.7411764706, blue: 0.7411764706, alpha: 1)
|
||||
separatorView.layer.cornerRadius = 0.5
|
||||
addSubview(separatorView)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var i = 0
|
||||
for oneText in items {
|
||||
let x = (width * CGFloat(i))
|
||||
|
||||
let view = UIView(frame: frame)
|
||||
view.set(x: x)
|
||||
let oneButton = UIButton(type: .system)
|
||||
oneButton.frame = view.bounds
|
||||
view.addSubview(oneButton)
|
||||
|
||||
/// Btn Size
|
||||
|
||||
view.backgroundColor = .clear
|
||||
oneButton.backgroundColor = .clear
|
||||
oneButton.style(selected: false)
|
||||
|
||||
/// Btn
|
||||
oneButton.setTitle(oneText , for: .normal)
|
||||
oneButton.contentVerticalAlignment = .center
|
||||
|
||||
if let font = UIFont(name: "TTNorms-Regular", size: 18) {
|
||||
oneButton.titleLabel?.font = font
|
||||
}
|
||||
|
||||
if i == index {
|
||||
oneButton.style(selected: true)
|
||||
}
|
||||
|
||||
oneButton.addTarget(self, action: #selector(buttonAction(_:)), for: .touchUpInside)
|
||||
|
||||
//oneButton.set(width: width)
|
||||
views.append(view)
|
||||
buttons.append(oneButton)
|
||||
//self.addSubview(oneButton)
|
||||
i += 1
|
||||
}
|
||||
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .horizontal
|
||||
stackView.distribution = .fillEqually
|
||||
|
||||
for btn in views {
|
||||
stackView.addArrangedSubview(btn)
|
||||
}
|
||||
|
||||
|
||||
seView = UIImageView(frame: frame)
|
||||
seView.selectedView()
|
||||
addSubview(seView)
|
||||
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.addSubview(stackView)
|
||||
|
||||
stackView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
|
||||
stackView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
|
||||
stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
|
||||
stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
|
||||
self.stackView = stackView
|
||||
}
|
||||
|
||||
@objc func buttonAction(_ button: UIButton) {
|
||||
|
||||
var i = 0
|
||||
for btn in buttons {
|
||||
btn.style(selected: false)
|
||||
|
||||
if btn == button {
|
||||
self.index = i
|
||||
changeIndex?(i)
|
||||
let x = (btn.frame.width * CGFloat(i))
|
||||
UIView.animate(withDuration: 0.35) {
|
||||
self.seView.set(x: x)
|
||||
}
|
||||
btn.style(selected: true)
|
||||
}
|
||||
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// DesignnGradientView.swift
|
||||
// ContainerControllerSwift_Example
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 04.09.2024.
|
||||
// Copyright © 2024 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class DesignnGradientView: UIView {
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
@IBInspectable var brDash: Int = 0
|
||||
|
||||
@IBInspectable var grColor1: UIColor?
|
||||
@IBInspectable var grColor2: UIColor?
|
||||
@IBInspectable var grColor3: UIColor?
|
||||
@IBInspectable var grStartPoint: CGPoint = .zero
|
||||
@IBInspectable var grEndPoint: CGPoint = .zero
|
||||
@IBInspectable var grRadial: Bool = false /// default: linear
|
||||
|
||||
func setup() {
|
||||
|
||||
if let foundView = viewWithTag(55) {
|
||||
foundView.removeFromSuperview()
|
||||
}
|
||||
|
||||
let d = DesignFigure_(frame: bounds)
|
||||
d.backgroundColor = .clear
|
||||
d.tag = 55
|
||||
d.cornerRadius = cornerRadius
|
||||
// d.blur = blur
|
||||
// d.image = image
|
||||
// d.imageMode = imageMode
|
||||
d.fillColor = fillColor
|
||||
d.brWidth = brWidth
|
||||
d.brColor = brColor
|
||||
d.brDash = brDash
|
||||
|
||||
d.grColor1 = grColor1
|
||||
d.grColor2 = grColor2
|
||||
d.grColor3 = grColor3
|
||||
// d.grColor4 = grColor4
|
||||
// d.grColor5 = grColor5
|
||||
// d.grColor6 = grColor6
|
||||
d.grStartPoint = grStartPoint
|
||||
d.grEndPoint = grEndPoint
|
||||
d.grRadial = grRadial
|
||||
// d.grDrawsOptions = grDrawsOptions
|
||||
// d.grDebug = grDebug
|
||||
// d.grPointPercent = grPointPercent
|
||||
// d.grBlendMode = grBlendMode
|
||||
|
||||
insertSubview(d, at: 0)
|
||||
|
||||
}
|
||||
|
||||
override func layoutSubviews() { setup() }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,674 @@
|
||||
//
|
||||
// DesignnGraphView.swift
|
||||
// ContainerControllerSwift_Example
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 04.09.2024.
|
||||
// Copyright © 2024 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
|
||||
@IBDesignable
|
||||
class DesignnGraphView: UIView {
|
||||
|
||||
|
||||
@IBInspectable var gr2Color1: UIColor?
|
||||
@IBInspectable var gr2Color2: UIColor?
|
||||
@IBInspectable var gr2Color3: UIColor?
|
||||
@IBInspectable var gr2StartPoint: CGPoint = .zero
|
||||
@IBInspectable var gr2EndPoint: CGPoint = .zero
|
||||
@IBInspectable var gr2BlendMode: Int = 0
|
||||
|
||||
@IBInspectable var grColor1: UIColor?
|
||||
@IBInspectable var grColor2: UIColor?
|
||||
@IBInspectable var grColor3: UIColor?
|
||||
@IBInspectable var grStartPoint: CGPoint = .zero
|
||||
@IBInspectable var grEndPoint: CGPoint = .zero
|
||||
@IBInspectable var grRadial: Bool = false /// default: linear
|
||||
@IBInspectable var grBlendMode: Int = 0
|
||||
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
@IBInspectable var padding: CGFloat = 0
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
|
||||
@IBInspectable var addPoints: Bool = true
|
||||
@IBInspectable var arrStr: String = ""
|
||||
|
||||
var data: [CGFloat] {
|
||||
get {
|
||||
|
||||
let result = arrStr.components(separatedBy: ",")
|
||||
|
||||
let newResult = result.map {
|
||||
CGFloat(Int($0) ?? 0)
|
||||
}
|
||||
return newResult
|
||||
}
|
||||
set {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() { setup() }
|
||||
|
||||
func setup() {
|
||||
self.layer.backgroundColor = UIColor.clear.cgColor
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.layer.cornerRadius = cornerRadius
|
||||
// self.cornerRadius = cornerRadius
|
||||
self.clipsToBounds = true
|
||||
|
||||
|
||||
let bezier = rectanglee(rect: bounds, radius: cornerRadius)
|
||||
|
||||
addFill(bezier: bezier, fillColor: fillColor)
|
||||
|
||||
// guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
let curve = quadCurvedPath()
|
||||
let path = curve.0
|
||||
let points = curve.1
|
||||
|
||||
addGradient(
|
||||
bezier: path,
|
||||
grColor1: gr2Color1,
|
||||
grColor2: gr2Color2,
|
||||
grColor3: gr2Color3,
|
||||
grStartPoint: gr2StartPoint,
|
||||
grEndPoint: gr2EndPoint,
|
||||
grBlendMode: gr2BlendMode
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
// context.saveGState()
|
||||
// path.addClip()
|
||||
// context.drawLinearGradient(gradient,
|
||||
// start: grStartPointR,
|
||||
// end: grEndPointR,
|
||||
// options: options)
|
||||
|
||||
|
||||
|
||||
// brColor.setStroke()
|
||||
// path.lineWidth = brWidth
|
||||
// path.stroke()
|
||||
|
||||
|
||||
gradientCurve(points: points)
|
||||
|
||||
|
||||
|
||||
// addInfoLabels()
|
||||
|
||||
// addGradient(bezier: bezier)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// if let foundView = viewWithTag(55) {
|
||||
// foundView.removeFromSuperview()
|
||||
// }
|
||||
//
|
||||
//
|
||||
// let d = DesignnGradientView(frame: bounds)
|
||||
// d.backgroundColor = .clear
|
||||
// d.tag = 44
|
||||
// d.fillColor = .clear
|
||||
// d.brWidth = brWidth
|
||||
// d.brColor = brColor
|
||||
// d.grColor1 = grColor1
|
||||
// d.grColor2 = grColor2
|
||||
// d.grColor3 = grColor3
|
||||
// d.grStartPoint = grStartPoint
|
||||
// d.grEndPoint = grEndPoint
|
||||
// d.layoutSubviews()
|
||||
//
|
||||
//
|
||||
// let g = DesignnGraphOnlyView(frame: bounds)
|
||||
// g.backgroundColor = .clear
|
||||
// g.tag = 44
|
||||
// g.fillColor = .clear
|
||||
// g.brWidth = brWidth
|
||||
// g.brColor = brColor
|
||||
// g.arrStr = arrStr
|
||||
// g.layoutSubviews()
|
||||
|
||||
|
||||
// let path = quadCurvedPath()
|
||||
// brColor.setStroke()
|
||||
// path.lineWidth = brWidth
|
||||
// path.stroke()
|
||||
|
||||
// var colors: [CGColor] = []
|
||||
// if let c = grColor1 { colors.append(c.cgColor) }
|
||||
// if let c = grColor2 { colors.append(c.cgColor) }
|
||||
// if let c = grColor3 { colors.append(c.cgColor) }
|
||||
//
|
||||
// let gradient = CAGradientLayer()
|
||||
// gradient.frame = path.bounds
|
||||
// gradient.colors = colors
|
||||
//
|
||||
// let shapeMask = CAShapeLayer()
|
||||
// shapeMask.path = path.cgPath
|
||||
//
|
||||
// gradient.mask = shapeMask
|
||||
// self.layer.addSublayer(gradient)
|
||||
|
||||
|
||||
// let newPath = convert(path, from: self)
|
||||
|
||||
|
||||
|
||||
// UIColor.orange.setFill()
|
||||
// newPath.fill()
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
func gradientCurve(points: [CGPoint]) {
|
||||
// Gradient for the chart colors
|
||||
let gradient = CAGradientLayer()
|
||||
|
||||
var colors: [CGColor] = []
|
||||
if let c = grColor1 { colors.append(c.cgColor) }
|
||||
if let c = grColor2 { colors.append(c.cgColor) }
|
||||
if let c = grColor3 { colors.append(c.cgColor) }
|
||||
gradient.colors = colors
|
||||
|
||||
|
||||
gradient.startPoint = grEndPoint
|
||||
gradient.endPoint = grStartPoint
|
||||
|
||||
gradient.frame = bounds
|
||||
layer.addSublayer(gradient)
|
||||
|
||||
// Random points
|
||||
let graph = CAShapeLayer()
|
||||
let path = CGMutablePath()
|
||||
// var y: CGFloat = 150
|
||||
// let points: [CGPoint] = stride(from: CGFloat.zero, to: 300, by: 2).map {
|
||||
// let change = CGFloat.random(in: -20...20)
|
||||
// var newY = y + change
|
||||
// newY = max(10, newY)
|
||||
// newY = min(newY, 300)
|
||||
// y = newY
|
||||
// return CGPoint(x: $0, y: y)
|
||||
// }
|
||||
path.addLines(between: points)
|
||||
graph.path = path
|
||||
graph.fillColor = nil
|
||||
graph.strokeColor = UIColor.black.cgColor
|
||||
graph.lineWidth = brWidth
|
||||
graph.lineJoin = .round
|
||||
graph.lineCap = .round
|
||||
graph.frame = bounds
|
||||
|
||||
// Only show the gradient where the line is
|
||||
gradient.mask = graph
|
||||
}
|
||||
|
||||
func convert(_ path: UIBezierPath, from view: UIView) -> UIBezierPath {
|
||||
path.apply(view.transform)
|
||||
// we now have a path in view's superview's coordinates. Let's adjust by the origin.
|
||||
let viewTransform = CGAffineTransform(translationX: view.frame.origin.x, y: view.frame.origin.y)
|
||||
path.apply(viewTransform)
|
||||
|
||||
// now the path is adjusted by view's origin. Next we need to subtract self's origin
|
||||
let selfTranslationTransform = CGAffineTransform(translationX: -self.frame.origin.x, y: -self.frame.origin.y)
|
||||
path.apply(selfTranslationTransform)
|
||||
|
||||
// now adjust by self's transform
|
||||
path.apply(self.transform.inverted())
|
||||
return path
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
|
||||
setup()
|
||||
// self.layer.cornerRadius = cornerRadius
|
||||
// self.layer.backgroundColor = fillColor?.cgColor
|
||||
// self.clipsToBounds = true
|
||||
//
|
||||
//
|
||||
// let bezier = rectangle(rect: bounds, radius: cornerRadius)
|
||||
//
|
||||
//// addFill(bezier: bezier)
|
||||
////
|
||||
//// addGradient(bezier: bezier)
|
||||
//
|
||||
//
|
||||
// let path = quadCurvedPath()
|
||||
//
|
||||
//
|
||||
// var colors: [CGColor] = []
|
||||
// if let c = grColor1 { colors.append(c.cgColor) }
|
||||
// if let c = grColor2 { colors.append(c.cgColor) }
|
||||
// if let c = grColor3 { colors.append(c.cgColor) }
|
||||
//
|
||||
// let gradient = CAGradientLayer()
|
||||
// gradient.frame = path.bounds
|
||||
// gradient.colors = colors
|
||||
//
|
||||
// let shapeMask = CAShapeLayer()
|
||||
// shapeMask.path = path.cgPath
|
||||
//
|
||||
// gradient.mask = shapeMask
|
||||
// self.layer.addSublayer(gradient)
|
||||
|
||||
// brColor.setStroke()
|
||||
// path.lineWidth = brWidth
|
||||
// path.stroke()
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
func getText(_ text: String, size: CGFloat = 12, position: CGPoint) -> CATextLayer {
|
||||
let textlayer = CATextLayer()
|
||||
textlayer.frame = CGRect(x: position.x, y: position.y, width: 30, height: 18)
|
||||
textlayer.fontSize = size
|
||||
textlayer.alignmentMode = .center
|
||||
textlayer.string = text
|
||||
textlayer.isWrapped = true
|
||||
textlayer.truncationMode = .end
|
||||
textlayer.backgroundColor = UIColor.clear.cgColor
|
||||
textlayer.foregroundColor = UIColor.black.cgColor
|
||||
return textlayer
|
||||
}
|
||||
|
||||
func getRect() -> CGSize {
|
||||
.init(width: bounds.width - (padding * 2), height: bounds.height - (padding * 2))
|
||||
}
|
||||
|
||||
func quadCurvedPath() -> (UIBezierPath, [CGPoint]) {
|
||||
|
||||
var arrPoints: [CGPoint] = []
|
||||
|
||||
let path = UIBezierPath()
|
||||
let step = (getRect().width) / CGFloat(data.count - 1)
|
||||
|
||||
var p1 = CGPoint(x: 0 + padding, y: coordYFor(index: 0) + padding)
|
||||
arrPoints.append(p1)
|
||||
path.move(to: p1)
|
||||
|
||||
if addPoints { drawPoint(point: p1, color: UIColor.red, radius: 3) }
|
||||
|
||||
if (data.count == 2) {
|
||||
let pp = CGPoint(x: step + padding, y: coordYFor(index: 1) + padding)
|
||||
arrPoints.append(pp)
|
||||
path.addLine(to: pp)
|
||||
return (path, arrPoints)
|
||||
}
|
||||
|
||||
var oldControlP: CGPoint?
|
||||
|
||||
for i in 1..<data.count {
|
||||
let p2 = CGPoint(x: (step * CGFloat(i)) + padding, y: coordYFor(index: i) + padding)
|
||||
if addPoints { drawPoint(point: p2, color: UIColor.red, radius: 3) }
|
||||
var p3: CGPoint?
|
||||
if i < data.count - 1 {
|
||||
p3 = CGPoint(x: (step * CGFloat(i + 1)) + padding, y: (coordYFor(index: i + 1)) + padding)
|
||||
}
|
||||
|
||||
let newControlP = controlPointForPoints(p1: p1, p2: p2, next: p3)
|
||||
|
||||
|
||||
arrPoints.append(p2)
|
||||
path.addCurve(to: p2, controlPoint1: oldControlP ?? p1, controlPoint2: newControlP ?? p2)
|
||||
|
||||
p1 = p2
|
||||
oldControlP = antipodalFor(point: newControlP, center: p2)
|
||||
}
|
||||
|
||||
|
||||
path.addLine(to: .init(x: getRect().width, y: getRect().height))
|
||||
path.addLine(to: .init(x: 0, y: getRect().height))
|
||||
|
||||
if arrPoints.count > 0 {
|
||||
path.addLine(to: arrPoints[0])
|
||||
}
|
||||
// path.close()
|
||||
|
||||
// arrPoints.reverse()
|
||||
//
|
||||
// var old: CGPoint = oldControlP ?? p1
|
||||
// arrPoints.forEach {
|
||||
// let new: CGPoint = .init(x: $0.x + 0.5, y: $0.y + 0.5)
|
||||
// path.addCurve(to: new, controlPoint1: old, controlPoint2: new)
|
||||
// old = new
|
||||
// }
|
||||
|
||||
|
||||
|
||||
return (path, arrPoints);
|
||||
}
|
||||
|
||||
/// located on the opposite side from the center point
|
||||
func antipodalFor(point: CGPoint?, center: CGPoint?) -> CGPoint? {
|
||||
guard let p1 = point, let center = center else {
|
||||
return nil
|
||||
}
|
||||
let newX = 2 * center.x - p1.x
|
||||
let diffY = abs(p1.y - center.y)
|
||||
let newY = center.y + diffY * (p1.y < center.y ? 1 : -1)
|
||||
|
||||
return CGPoint(x: newX, y: newY)
|
||||
}
|
||||
|
||||
/// halfway of two points
|
||||
func midPointForPoints(p1: CGPoint, p2: CGPoint) -> CGPoint {
|
||||
return CGPoint(x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2);
|
||||
}
|
||||
|
||||
/// Find controlPoint2 for addCurve
|
||||
/// - Parameters:
|
||||
/// - p1: first point of curve
|
||||
/// - p2: second point of curve whose control point we are looking for
|
||||
/// - next: predicted next point which will use antipodal control point for finded
|
||||
func controlPointForPoints(p1: CGPoint, p2: CGPoint, next p3: CGPoint?) -> CGPoint? {
|
||||
guard let p3 = p3 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let leftMidPoint = midPointForPoints(p1: p1, p2: p2)
|
||||
let rightMidPoint = midPointForPoints(p1: p2, p2: p3)
|
||||
|
||||
var controlPoint = midPointForPoints(p1: leftMidPoint, p2: antipodalFor(point: rightMidPoint, center: p2)!)
|
||||
|
||||
if p1.y.between(a: p2.y, b: controlPoint.y) {
|
||||
controlPoint.y = p1.y
|
||||
} else if p2.y.between(a: p1.y, b: controlPoint.y) {
|
||||
controlPoint.y = p2.y
|
||||
}
|
||||
|
||||
|
||||
let imaginContol = antipodalFor(point: controlPoint, center: p2)!
|
||||
if p2.y.between(a: p3.y, b: imaginContol.y) {
|
||||
controlPoint.y = p2.y
|
||||
}
|
||||
if p3.y.between(a: p2.y, b: imaginContol.y) {
|
||||
let diffY = abs(p2.y - p3.y)
|
||||
controlPoint.y = p2.y + diffY * (p3.y < p2.y ? 1 : -1)
|
||||
}
|
||||
|
||||
// make lines easier
|
||||
controlPoint.x += (p2.x - p1.x) * 0.1
|
||||
|
||||
return controlPoint
|
||||
}
|
||||
|
||||
func coordYFor(index: Int) -> CGFloat {
|
||||
return getRect().height - getRect().height * data[index] / (data.max() ?? 0)
|
||||
}
|
||||
|
||||
func drawPoint(point: CGPoint, color: UIColor, radius: CGFloat) {
|
||||
let ovalPath = UIBezierPath(ovalIn: CGRect(x: point.x - radius, y: point.y - radius, width: radius * 2, height: radius * 2))
|
||||
color.setFill()
|
||||
ovalPath.fill()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension CGFloat {
|
||||
func between(a: CGFloat, b: CGFloat) -> Bool {
|
||||
return self >= Swift.min(a, b) && self <= Swift.max(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
func rectanglee(rect: CGRect, radius: CGFloat) -> UIBezierPath {
|
||||
|
||||
let r = self.cornerRadius(radius)
|
||||
let w = rect.width
|
||||
let h = rect.height
|
||||
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: r, y: 0.0))
|
||||
|
||||
path.addLine(to: CGPoint(x: w - r, y: 0.0))
|
||||
path.addArc(withCenter: CGPoint(x: w - r, y: r),
|
||||
radius: r,
|
||||
startAngle: 3.0 * .pi / 2.0,
|
||||
endAngle: 2 * .pi,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: w, y: h - r))
|
||||
path.addArc(withCenter: CGPoint(x: w - r, y: h - r),
|
||||
radius: r,
|
||||
startAngle: 0.0,
|
||||
endAngle: .pi / 2.0,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: r, y: h))
|
||||
path.addArc(withCenter: CGPoint(x: r, y: h - r),
|
||||
radius: r,
|
||||
startAngle: .pi / 2.0,
|
||||
endAngle: .pi,
|
||||
clockwise: true)
|
||||
|
||||
path.addLine(to: CGPoint(x: 0.0, y: r))
|
||||
path.addArc(withCenter: CGPoint(x: r, y: r),
|
||||
radius: r,
|
||||
startAngle: .pi,
|
||||
endAngle: 3.0 * .pi / 2.0,
|
||||
clockwise: true)
|
||||
|
||||
path.close()
|
||||
return path
|
||||
}
|
||||
|
||||
|
||||
func drawLinearGradient(inside path:UIBezierPath, start:CGPoint, end:CGPoint, colors:[UIColor])
|
||||
{
|
||||
guard let ctx = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
ctx.saveGState()
|
||||
defer { ctx.restoreGState() } // clean up graphics state changes when the method returns
|
||||
|
||||
path.addClip() // use the path as the clipping region
|
||||
|
||||
let cgColors = colors.map({ $0.cgColor })
|
||||
guard let gradient = CGGradient(colorsSpace: nil, colors: cgColors as CFArray, locations: nil)
|
||||
else { return }
|
||||
|
||||
ctx.drawLinearGradient(gradient, start: start, end: end, options: [])
|
||||
}
|
||||
}
|
||||
|
||||
extension DesignnGraphView {
|
||||
|
||||
//MARK: - Gradient
|
||||
|
||||
private func addGradient(
|
||||
bezier: UIBezierPath,
|
||||
grColor1: UIColor?,
|
||||
grColor2: UIColor?,
|
||||
grColor3: UIColor?,
|
||||
grStartPoint: CGPoint,
|
||||
grEndPoint: CGPoint,
|
||||
grBlendMode: Int = 0
|
||||
) {
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
var colors: [CGColor] = []
|
||||
|
||||
if let c = grColor1 { colors.append(c.cgColor) }
|
||||
if let c = grColor2 { colors.append(c.cgColor) }
|
||||
if let c = grColor3 { colors.append(c.cgColor) }
|
||||
// if let c = grColor4 { colors.append(c.cgColor) }
|
||||
// if let c = grColor5 { colors.append(c.cgColor) }
|
||||
// if let c = grColor6 { colors.append(c.cgColor) }
|
||||
|
||||
if colors.count < 2 {
|
||||
colors.append(UIColor.clear.cgColor)
|
||||
colors.append(UIColor.clear.cgColor)
|
||||
} else if colors.count < 1 {
|
||||
if grColor1 != nil {
|
||||
colors.append(UIColor.clear.cgColor)
|
||||
} else {
|
||||
colors.insert(UIColor.clear.cgColor, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
guard let gradient = CGGradient(colorsSpace: nil,
|
||||
colors: colors as CFArray,
|
||||
locations: nil) else { return }
|
||||
let grDrawsOptions: Bool = true
|
||||
let grPointPercent: Bool = true
|
||||
|
||||
let bMode = CGBlendMode(rawValue: CGBlendMode.RawValue(grBlendMode)) ?? CGBlendMode.sourceAtop
|
||||
|
||||
// bMode = CGBlendMode.screen
|
||||
|
||||
context.setBlendMode(bMode)
|
||||
|
||||
let options: CGGradientDrawingOptions =
|
||||
grDrawsOptions ? [ .drawsBeforeStartLocation, .drawsAfterEndLocation ] : [ ]
|
||||
|
||||
var grEndPointR: CGPoint = grEndPoint
|
||||
var grStartPointR: CGPoint = grStartPoint
|
||||
if grPointPercent {
|
||||
grEndPointR = CGPoint(x: (grEndPoint.x * frame.width), y: (grEndPoint.y * frame.height))
|
||||
grStartPointR = CGPoint(x: (grStartPoint.x * frame.width), y: (grStartPoint.y * frame.height))
|
||||
}
|
||||
|
||||
bezier.close()
|
||||
context.saveGState()
|
||||
bezier.addClip()
|
||||
|
||||
if grRadial {
|
||||
|
||||
let x: CGFloat = (grEndPointR.x - grStartPointR.x)
|
||||
let y: CGFloat = (grEndPointR.y - grStartPointR.y)
|
||||
let distance: CGFloat = sqrt((x * x) + (y * y))
|
||||
|
||||
context.drawRadialGradient(gradient,
|
||||
startCenter: grStartPointR,
|
||||
startRadius: 0,
|
||||
endCenter: grStartPointR,
|
||||
endRadius: distance,
|
||||
options: options)
|
||||
} else {
|
||||
|
||||
context.drawLinearGradient(gradient,
|
||||
start: grStartPointR,
|
||||
end: grEndPointR,
|
||||
options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DesignnGraphView {
|
||||
|
||||
func addInfoLabels() {
|
||||
|
||||
let mmaax = Int(data.max() ?? 0)
|
||||
|
||||
self.layer.addSublayer(getText("0" ,size: 13, position: .init(x: 0, y: bounds.height - 18 )))
|
||||
self.layer.addSublayer(getText("\(mmaax)" ,size: 13, position: .init(x: 0, y: 0 )))
|
||||
self.layer.addSublayer(getText("\(data.count)" ,size: 13, position: .init(x: bounds.width - 30, y: bounds.height - 18 )))
|
||||
|
||||
var start = false
|
||||
var half = false
|
||||
var end = false
|
||||
|
||||
for i in 0..<mmaax {
|
||||
|
||||
|
||||
let y = (( CGFloat(i) / CGFloat(mmaax) ) * (bounds.height - 18))
|
||||
|
||||
let percent = Int((y / bounds.height) * 100)
|
||||
|
||||
var str = "\(100 - percent )%"
|
||||
str = "--"
|
||||
|
||||
if percent >= 25 && !start {
|
||||
start = true
|
||||
self.layer.addSublayer(getText( str ,size: 10, position: .init(x: 0 , y: y - 9 )))
|
||||
self.layer.addSublayer(getText( str ,size: 10, position: .init(x: bounds.width - 30 , y: y - 9 )))
|
||||
}
|
||||
|
||||
if percent >= 50 && !half {
|
||||
half = true
|
||||
str = "=--"
|
||||
|
||||
self.layer.addSublayer(getText(str,size: 10, position: .init(x: 0 , y: y - 9 )))
|
||||
str = "--="
|
||||
self.layer.addSublayer(getText( str ,size: 10, position: .init(x: bounds.width - 30 , y: y - 9 )))
|
||||
}
|
||||
|
||||
|
||||
if percent >= 75 && !end {
|
||||
end = true
|
||||
self.layer.addSublayer(getText(str,size: 10, position: .init(x:0, y:y - 9)))
|
||||
self.layer.addSublayer(getText( str ,size: 10, position: .init(x: bounds.width - 30 , y: y - 9 )))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
start = false
|
||||
half = false
|
||||
end = false
|
||||
|
||||
for i in 0..<data.count {
|
||||
|
||||
let x = (( CGFloat(i) / CGFloat(data.count) ) * (bounds.width - 30))
|
||||
|
||||
let percent = Int((x / bounds.width) * 100)
|
||||
|
||||
var str = "\(percent)%"
|
||||
str = "!"
|
||||
|
||||
if percent >= 25 && !start {
|
||||
start = true
|
||||
self.layer.addSublayer(getText(str,size: 10, position: .init(x: x - 15 , y: bounds.height - 18 )))
|
||||
self.layer.addSublayer(getText(str,size: 10, position: .init(x: x - 15 , y: 5 )))
|
||||
}
|
||||
|
||||
if percent >= 50 && !half {
|
||||
half = true
|
||||
str = ".|."
|
||||
self.layer.addSublayer(getText(str,size: 10, position: .init(x: x - 15 , y: bounds.height - 18 )))
|
||||
str = "'|'"
|
||||
self.layer.addSublayer(getText(str,size: 10, position: .init(x: x - 15 , y: 5 )))
|
||||
}
|
||||
|
||||
|
||||
if percent >= 75 && !end {
|
||||
end = true
|
||||
self.layer.addSublayer(getText(str,size: 10, position: .init(x: x - 15, y: bounds.height - 18 )))
|
||||
self.layer.addSublayer(getText(str,size: 10, position: .init(x: x - 15 , y: 5 )))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
func addFill(bezier: UIBezierPath, fillColor: UIColor?) {
|
||||
|
||||
guard let fillColor = fillColor else { return }
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
bezier.close()
|
||||
context.saveGState()
|
||||
fillColor.setFill()
|
||||
bezier.fill()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
//
|
||||
// DesignnGraphViewLegacy.swift
|
||||
// ContainerControllerSwift_Example
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 04.09.2024.
|
||||
// Copyright © 2024 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class DesignnGraphViewOld: UIView {
|
||||
|
||||
@IBInspectable var maxValue: Int = 100
|
||||
@IBInspectable var minValue: Int = 0
|
||||
|
||||
@IBInspectable var padding: Int = 10
|
||||
|
||||
@IBInspectable var arrStr: String = ""
|
||||
|
||||
var arrData: [Int] = [10,5,50,70,80,40,35,55,60,62,20,40,70,60,50,90,95]
|
||||
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
@IBInspectable var brDash: Int = 0
|
||||
|
||||
|
||||
func addFill(bezier: UIBezierPath) {
|
||||
|
||||
guard let fillColor = fillColor else { return }
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
bezier.close()
|
||||
context.saveGState()
|
||||
fillColor.setFill()
|
||||
bezier.fill()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
|
||||
// layer.backgroundColor = fillColor?.cgColor
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
let oval = UIBezierPath(ovalIn: rect)
|
||||
addFill(bezier: oval)
|
||||
|
||||
|
||||
UIColor.green.setStroke()
|
||||
|
||||
oval.lineWidth = 2 * 2
|
||||
oval.stroke()
|
||||
|
||||
//// Color Declarations
|
||||
let gradientColor = UIColor(red: 0.039, green: 0.839, blue: 0.361, alpha: 0.140)
|
||||
let gradientColor2 = UIColor(red: 0.039, green: 0.841, blue: 0.362, alpha: 0.000)
|
||||
let gradientColor3 = UIColor.green
|
||||
let color = UIColor.red
|
||||
|
||||
//// Gradient Declarations
|
||||
// let gradient = CGGradient(colorsSpace: nil, colors: [ gradientColor.cgColor, gradientColor2.cgColor] as CFArray, locations: [0, 1])!
|
||||
//// Rectangle Drawing
|
||||
// let rectanglePath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 70, height: 70))
|
||||
// color.setFill()
|
||||
// rectanglePath.fill()
|
||||
|
||||
|
||||
//// Bezier Drawing
|
||||
let bezierPath = UIBezierPath ()
|
||||
|
||||
UIColor.green.setStroke()
|
||||
bezierPath.lineWidth = 2 * 2
|
||||
|
||||
|
||||
// let result = arrStr.components(separatedBy: ",")
|
||||
//
|
||||
// let newResult = result.map {
|
||||
// Int($0) ?? 0
|
||||
// }
|
||||
|
||||
|
||||
arrData.enumerated().forEach { index, item in
|
||||
let widht = ( (rect.width) - ( CGFloat(padding) * 2))
|
||||
let height = ( (rect.height) - (CGFloat(padding) * 2))
|
||||
|
||||
|
||||
let y = ((( CGFloat(index) / CGFloat(arrData.count) ) * widht) + CGFloat(padding))
|
||||
|
||||
let maxHeight = CGFloat(arrData.max() ?? 0)
|
||||
|
||||
let x = ((( CGFloat(item) / maxHeight) * height) + CGFloat(padding))
|
||||
|
||||
|
||||
bezierPath.move(to: CGPoint(x: x, y: y))
|
||||
}
|
||||
|
||||
|
||||
// bezierPath.move(to: CGPoint(x: 4, y: 34.64))
|
||||
// bezierPath.addLine(to: CGPoint(x: 8.4, y: 34.64))
|
||||
// bezierPath.addLine(to: CGPoint(x: 11.04, y: 32.04))
|
||||
// bezierPath.addLine(to: CGPoint(x: 15.44, y: 32.04))
|
||||
// bezierPath.addLine(to: CGPoint(x: 18.53, y: 29.01))
|
||||
// bezierPath.addLine(to: CGPoint(x: 22.49, y: 29.01))
|
||||
// bezierPath.addLine(to: CGPoint(x: 26.01, y: 34.64))
|
||||
// bezierPath.addLine(to: CGPoint(x: 29.53, y: 37.89))
|
||||
// bezierPath.addLine(to: CGPoint(x: 32.61, y: 37.89))
|
||||
// bezierPath.addLine(to: CGPoint(x: 34.81, y: 34.64))
|
||||
// bezierPath.addLine(to: CGPoint(x: 38.55, y: 34.64))
|
||||
// bezierPath.addLine(to: CGPoint(x: 42.95, y: 25.55))
|
||||
// bezierPath.addLine(to: CGPoint(x: 53.08, y: 25.55))
|
||||
// bezierPath.addLine(to: CGPoint(x: 55.5, y: 21))
|
||||
// bezierPath.addLine(to: CGPoint(x: 61, y: 21))
|
||||
// bezierPath.addLine(to: CGPoint(x: 61, y: 42))
|
||||
// bezierPath.addLine(to: CGPoint(x: 4, y: 42))
|
||||
// bezierPath.addLine(to: CGPoint(x: 4, y: 34.64))
|
||||
|
||||
bezierPath.stroke()
|
||||
// bezierPath.stroke()
|
||||
|
||||
context.restoreGState()
|
||||
// context.restoreGState()
|
||||
|
||||
|
||||
// bezierPath.close()
|
||||
// context.saveGState()
|
||||
// context.restoreGState()
|
||||
|
||||
// bezierPath.close ()
|
||||
// context.saveGState ()
|
||||
// bezierPath.addClip()
|
||||
// context.drawLinearGradient(gradient, start: CGPoint(x: 362.5, y: 84), end: CGPoint(x: 362.5, y: 105), options: [])
|
||||
// context.restoreGState()
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
func getPoints() {
|
||||
|
||||
var arr: [CGPoint] = []
|
||||
|
||||
print(" getPoints width: \(self.width) height: \(self.height)")
|
||||
arrData.enumerated().forEach { index, item in
|
||||
|
||||
let widht = ( (self.width) - ( CGFloat(padding) * 2))
|
||||
let height = ( (self.height) - (CGFloat(padding) * 2))
|
||||
|
||||
|
||||
let y = ((( CGFloat(index) / CGFloat(arrData.count) ) * widht) + CGFloat(padding))
|
||||
|
||||
let maxHeight = CGFloat(arrData.max() ?? 0)
|
||||
|
||||
let x = ((( CGFloat(item) / maxHeight) * height) + CGFloat(padding))
|
||||
|
||||
print(" getPoints ( width: \(widht) height: \(height) maxHeight \(maxHeight)) index: \(index), x: \(x) y: \(y)")
|
||||
|
||||
arr.append(CGPoint(x: x, y: y))
|
||||
// bezierPath.move(to: CGPoint(x: x, y: y))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// func setup() {
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// if let foundView = viewWithTag(55) {
|
||||
// foundView.removeFromSuperview()
|
||||
// }
|
||||
//
|
||||
// let d = DesignFigure_(frame: bounds)
|
||||
// d.backgroundColor = .clear
|
||||
// d.tag = 55
|
||||
// d.cornerRadius = cornerRadius
|
||||
// // d.blur = blur
|
||||
// // d.image = image
|
||||
// // d.imageMode = imageMode
|
||||
// d.fillColor = fillColor
|
||||
// d.brWidth = brWidth
|
||||
// d.brColor = brColor
|
||||
// d.brDash = brDash
|
||||
// insertSubview(d, at: 0)
|
||||
//
|
||||
// }
|
||||
|
||||
// override func layoutSubviews() { setup() }
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// DesignnImageView.swift
|
||||
// ContainerControllerSwift_Example
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 04.09.2024.
|
||||
// Copyright © 2024 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class DesignnImageView: UIView {
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
@IBInspectable var image: UIImage?
|
||||
@IBInspectable var imageMode: Int = 1
|
||||
@IBInspectable var blur: CGFloat = 0.0
|
||||
|
||||
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
@IBInspectable var brDash: Int = 0
|
||||
|
||||
func setup() {
|
||||
|
||||
if let foundView = viewWithTag(55) {
|
||||
foundView.removeFromSuperview()
|
||||
}
|
||||
|
||||
let d = DesignFigure_(frame: bounds)
|
||||
d.backgroundColor = .clear
|
||||
d.tag = 55
|
||||
d.cornerRadius = cornerRadius
|
||||
d.blur = blur
|
||||
d.image = image
|
||||
d.imageMode = imageMode
|
||||
d.fillColor = fillColor
|
||||
d.brWidth = brWidth
|
||||
d.brColor = brColor
|
||||
d.brDash = brDash
|
||||
insertSubview(d, at: 0)
|
||||
|
||||
}
|
||||
|
||||
override func layoutSubviews() { setup() }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// DesignnShadowView.swift
|
||||
// ContainerControllerSwift_Example
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 04.09.2024.
|
||||
// Copyright © 2024 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class DesignnShadowView: UIView {
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
|
||||
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
@IBInspectable var brDash: Int = 0
|
||||
|
||||
@IBInspectable var shColor: UIColor = .clear
|
||||
@IBInspectable var shRadius: CGFloat = 0.0
|
||||
@IBInspectable var shOffset: CGSize = .zero
|
||||
|
||||
|
||||
func setup() {
|
||||
|
||||
if let foundView = viewWithTag(55) {
|
||||
foundView.removeFromSuperview()
|
||||
}
|
||||
|
||||
let d = DesignFigure_(frame: bounds)
|
||||
d.backgroundColor = .clear
|
||||
d.tag = 55
|
||||
d.cornerRadius = cornerRadius
|
||||
// d.blur = blur
|
||||
// d.image = image
|
||||
// d.imageMode = imageMode
|
||||
d.fillColor = fillColor
|
||||
d.brWidth = brWidth
|
||||
d.brColor = brColor
|
||||
d.brDash = brDash
|
||||
|
||||
|
||||
d.shColor = shColor
|
||||
d.shRadius = shRadius
|
||||
d.shOffset = shOffset
|
||||
insertSubview(d, at: 0)
|
||||
|
||||
}
|
||||
|
||||
override func layoutSubviews() { setup() }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// DesignnView.swift
|
||||
// ContainerControllerSwift_Example
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 04.09.2024.
|
||||
// Copyright © 2024 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class DesignnView: UIView {
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
@IBInspectable var fillColor: UIColor?
|
||||
|
||||
|
||||
@IBInspectable var brColor: UIColor = .clear
|
||||
@IBInspectable var brWidth: CGFloat = 0.0
|
||||
@IBInspectable var brDash: Int = 0
|
||||
|
||||
func setup() {
|
||||
|
||||
if let foundView = viewWithTag(55) {
|
||||
foundView.removeFromSuperview()
|
||||
}
|
||||
|
||||
let d = DesignFigure_(frame: bounds)
|
||||
d.backgroundColor = .clear
|
||||
d.tag = 55
|
||||
d.cornerRadius = cornerRadius
|
||||
// d.blur = blur
|
||||
// d.image = image
|
||||
// d.imageMode = imageMode
|
||||
d.fillColor = fillColor
|
||||
d.brWidth = brWidth
|
||||
d.brColor = brColor
|
||||
d.brDash = brDash
|
||||
insertSubview(d, at: 0)
|
||||
|
||||
}
|
||||
|
||||
// override func layoutSubviews() { setup() }
|
||||
|
||||
}
|
||||
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
//
|
||||
// DesignViewExtentions.swift
|
||||
// FigmaConvertXib
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 20.07.2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
func add(shadow radius: CGFloat, offset: CGSize, color: UIColor) {
|
||||
layer.shadowOpacity = 1.0
|
||||
layer.shadowColor = color.cgColor
|
||||
layer.shadowOffset = offset
|
||||
layer.shadowRadius = radius
|
||||
}
|
||||
|
||||
func add(border width: CGFloat, color: UIColor) {
|
||||
layer.borderColor = color.cgColor
|
||||
layer.borderWidth = width
|
||||
}
|
||||
|
||||
//MARK: - Blur
|
||||
|
||||
func add(blur: CGFloat, rect: CGRect? = nil, clearCallback: (() -> Void)? = nil) {
|
||||
|
||||
guard blur > 0 else { return }
|
||||
|
||||
layer.shadowOpacity = 0.0
|
||||
var fr = bounds
|
||||
if let rect = rect {
|
||||
fr = CGRect(x: 0, y: 0, width: rect.width, height: rect.height)
|
||||
}
|
||||
|
||||
UIGraphicsBeginImageContext(fr.size)
|
||||
layer.render(in: UIGraphicsGetCurrentContext()!)
|
||||
guard let imgC = UIGraphicsGetImageFromCurrentImageContext() else { return }
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
self.blur(screen: imgC, blur: blur, clearCallback: clearCallback)
|
||||
}
|
||||
|
||||
func blur(screen: UIImage, blur: CGFloat, clearCallback: (() -> Void)? = nil) {
|
||||
|
||||
guard let ciscreen: CIImage = CIImage(image: screen) else { return }
|
||||
|
||||
guard let filter: CIFilter = CIFilter(name: "CIGaussianBlur") else { return }
|
||||
filter.setDefaults()
|
||||
filter.setValue(ciscreen, forKey: kCIInputImageKey)
|
||||
filter.setValue(blur, forKey: kCIInputRadiusKey)
|
||||
|
||||
let bl2 = ((blur * 2) * -1)
|
||||
let bl4 = (blur * 4)
|
||||
|
||||
let ofW = (layer.shadowOffset.width * 1) * -1
|
||||
let ofH = (layer.shadowOffset.height * 1) * -1
|
||||
|
||||
let ofW2 = (layer.shadowOffset.width * 2)
|
||||
let ofH2 = (layer.shadowOffset.height * 2)
|
||||
|
||||
let contextFrame = CGRect(x: bl2 + ofW,
|
||||
y: bl2 + ofH,
|
||||
width: frame.width + bl4 + ofW2,
|
||||
height: frame.height + bl4 + ofH2)
|
||||
|
||||
let ciContext = CIContext(options: nil)
|
||||
guard let r = filter.value(forKey: kCIOutputImageKey) as? CIImage else { return }
|
||||
guard let cImg = ciContext.createCGImage(r, from: contextFrame) else { return }
|
||||
|
||||
let finalImage = UIImage(cgImage: cImg)
|
||||
|
||||
let imageFrame = CGRect(x: bl2 + ofW,
|
||||
y: bl2 + ofH,
|
||||
width: frame.width + bl4 + ofW2,
|
||||
height: frame.height + bl4 + ofH2)
|
||||
|
||||
let blurImageView = UIImageView(frame: imageFrame)
|
||||
blurImageView.image = finalImage
|
||||
blurImageView.contentMode = .scaleAspectFit
|
||||
|
||||
clearAllFills()
|
||||
|
||||
clearCallback?()
|
||||
|
||||
addSubview(blurImageView)
|
||||
|
||||
}
|
||||
|
||||
//MARK: - Blur Clear All
|
||||
|
||||
func clearAllFills() {
|
||||
|
||||
if let sublayers = layer.sublayers {
|
||||
for layer in sublayers {
|
||||
|
||||
layer.backgroundColor = UIColor.clear.cgColor
|
||||
if let gradientLayer = layer as? CAGradientLayer {
|
||||
gradientLayer.removeFromSuperlayer()
|
||||
} else if layer.name == "InnerShadow" {
|
||||
layer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layer.backgroundColor = UIColor.clear.cgColor
|
||||
|
||||
func clearShadow() {
|
||||
layer.shadowOffset = .zero
|
||||
layer.shadowOpacity = 0.0
|
||||
layer.shadowRadius = 0.0
|
||||
layer.shadowColor = UIColor.clear.cgColor
|
||||
}
|
||||
|
||||
func clearBorder() {
|
||||
layer.borderColor = UIColor.clear.cgColor
|
||||
layer.borderWidth = 0.0
|
||||
}
|
||||
|
||||
clearBorder()
|
||||
}
|
||||
|
||||
//MARK: - Inner Shadow
|
||||
|
||||
func addInnerShadow(color: UIColor,
|
||||
radius: CGFloat,
|
||||
offset: CGSize,
|
||||
cornerRadius: CGFloat,
|
||||
alpha: Float = 1.0) {
|
||||
|
||||
let cornerRadius = self.cornerRadius(cornerRadius)
|
||||
|
||||
let innerShadow = CALayer()
|
||||
innerShadow.name = "InnerShadow"
|
||||
innerShadow.frame = bounds
|
||||
|
||||
let path = UIBezierPath(roundedRect: innerShadow.bounds.insetBy(dx: -bounds.width, dy: -bounds.height), cornerRadius:cornerRadius)
|
||||
let cutout = UIBezierPath(roundedRect: innerShadow.bounds, cornerRadius:cornerRadius).reversing()
|
||||
|
||||
path.append(cutout)
|
||||
innerShadow.shadowPath = path.cgPath
|
||||
innerShadow.masksToBounds = true
|
||||
|
||||
innerShadow.shadowColor = color.cgColor
|
||||
innerShadow.shadowOffset = offset
|
||||
innerShadow.shadowRadius = radius
|
||||
innerShadow.cornerRadius = cornerRadius
|
||||
innerShadow.shadowOpacity = alpha
|
||||
layer.addSublayer(innerShadow)
|
||||
}
|
||||
|
||||
func add(gradient
|
||||
color1: UIColor? = nil,
|
||||
color2: UIColor? = nil,
|
||||
color3: UIColor? = nil,
|
||||
color4: UIColor? = nil,
|
||||
color5: UIColor? = nil,
|
||||
color6: UIColor? = nil,
|
||||
pointStart: CGPoint,
|
||||
pointEnd: CGPoint,
|
||||
cornerRadius: CGFloat) -> CAGradientLayer? {
|
||||
|
||||
if color1 != nil || color2 != nil || color3 != nil || color4 != nil || color5 != nil || color6 != nil {
|
||||
|
||||
var colors: [CGColor] = []
|
||||
// colors.append(fillColor.cgColor)
|
||||
if let color1 = color1 { colors.append(color1.cgColor) }
|
||||
if let color2 = color2 { colors.append(color2.cgColor) }
|
||||
if let color3 = color3 { colors.append(color3.cgColor) }
|
||||
if let color4 = color4 { colors.append(color4.cgColor) }
|
||||
if let color5 = color5 { colors.append(color5.cgColor) }
|
||||
if let color6 = color6 { colors.append(color6.cgColor) }
|
||||
|
||||
let glayer = CAGradientLayer()
|
||||
glayer.name = "Gradient"
|
||||
glayer.frame = bounds
|
||||
glayer.colors = colors
|
||||
glayer.startPoint = pointStart
|
||||
glayer.endPoint = pointEnd
|
||||
glayer.cornerRadius = self.cornerRadius(cornerRadius)
|
||||
layer.insertSublayer(glayer, at: 0)
|
||||
|
||||
layer.backgroundColor = UIColor.clear.cgColor
|
||||
|
||||
return glayer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
//MARK: - Check CornerRadius
|
||||
|
||||
func cornerRadius(_ cornerRadius: CGFloat) -> CGFloat {
|
||||
let minSize = min(frame.size.width, frame.size.height)
|
||||
let minSizeRadius = (minSize / 2)
|
||||
let radius = ((cornerRadius < 0) || (minSizeRadius < cornerRadius) ? minSizeRadius : cornerRadius)
|
||||
return radius
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class DesignButton: UIButton {
|
||||
|
||||
@IBInspectable var hideAnimation: Bool = true
|
||||
|
||||
@IBInspectable var fillColor: UIColor = .clear
|
||||
|
||||
@IBInspectable var gradientColor: UIColor?
|
||||
@IBInspectable var gradientOffset: CGPoint = CGPoint(x: 0, y: 1)
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
@IBInspectable var shadowColor: UIColor = .clear
|
||||
@IBInspectable var shadowOffset: CGSize = CGSize.zero
|
||||
@IBInspectable var shadowRadius: CGFloat = 0.0
|
||||
@IBInspectable var shadowOpacity: CGFloat = 0.0
|
||||
|
||||
@IBInspectable var borderColor: UIColor = .clear
|
||||
@IBInspectable var borderWidth: CGFloat = 0.0
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
|
||||
if let gradientColor = gradientColor {
|
||||
|
||||
let glayer = CAGradientLayer()
|
||||
glayer.frame = bounds
|
||||
glayer.colors = [fillColor.cgColor, gradientColor.cgColor]
|
||||
glayer.startPoint = CGPoint.zero
|
||||
glayer.endPoint = gradientOffset
|
||||
glayer.cornerRadius = radius()
|
||||
layer.insertSublayer(glayer, at: 0)
|
||||
|
||||
} else {
|
||||
layer.backgroundColor = fillColor.cgColor
|
||||
}
|
||||
|
||||
layer.cornerRadius = radius()
|
||||
|
||||
layer.shadowOffset = shadowOffset
|
||||
layer.shadowOpacity = Float(shadowOpacity / 10.0)
|
||||
layer.shadowRadius = shadowRadius
|
||||
layer.shadowColor = shadowColor.cgColor
|
||||
|
||||
layer.borderColor = borderColor.cgColor
|
||||
layer.borderWidth = borderWidth
|
||||
}
|
||||
|
||||
override var isSelected: Bool {
|
||||
didSet {
|
||||
super.isSelected = isSelected
|
||||
}
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
didSet {
|
||||
// super.isHighlighted = isHighlighted
|
||||
}
|
||||
}
|
||||
|
||||
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
selectedLayer(show: true)
|
||||
return super.beginTracking(touch, with: event)
|
||||
}
|
||||
|
||||
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
let point = touch.location(in: self)
|
||||
let someFrame = bounds
|
||||
let highlighted = someFrame.contains(point)
|
||||
selectedLayer(show: highlighted)
|
||||
return super.continueTracking(touch, with: event)
|
||||
}
|
||||
|
||||
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||
selectedLayer(show: false)
|
||||
super.endTracking(touch, with: event)
|
||||
}
|
||||
|
||||
override func cancelTracking(with event: UIEvent?) {
|
||||
selectedLayer(show: false)
|
||||
super.cancelTracking(with: event)
|
||||
}
|
||||
|
||||
func radius() -> CGFloat {
|
||||
let minSize = min(frame.size.width, frame.size.height)
|
||||
let radius = ((cornerRadius < 0) ? (minSize / 2) : cornerRadius)
|
||||
return radius
|
||||
}
|
||||
|
||||
func selectedLayer(show: Bool) {
|
||||
|
||||
func selectAnimationHide(show: Bool) {
|
||||
if show {
|
||||
alpha = 0.5
|
||||
} else {
|
||||
UIView.animate(withDuration: 0.35, animations: {
|
||||
self.alpha = 1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func selectAnimationShadow(show: Bool) {
|
||||
let tag = 1
|
||||
let view = viewWithTag(tag)
|
||||
if show {
|
||||
if view == nil {
|
||||
let view = UIView(frame: bounds)
|
||||
view.backgroundColor = UIColor.init(white: 0.0, alpha: 0.2)
|
||||
view.tag = tag
|
||||
view.layer.cornerRadius = radius()
|
||||
addSubview(view)
|
||||
}
|
||||
} else {
|
||||
if let view = view {
|
||||
UIView.animate(withDuration: 0.35, animations: {
|
||||
view.alpha = 0
|
||||
}) { (fin: Bool) in
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hideAnimation {
|
||||
selectAnimationHide(show: show)
|
||||
} else {
|
||||
selectAnimationShadow(show: show)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
class DesignView: UIView {
|
||||
|
||||
@IBInspectable var fillColor: UIColor = .clear
|
||||
|
||||
//MARK: - Gradient
|
||||
|
||||
@IBInspectable var gradientColor: UIColor?
|
||||
@IBInspectable var gradientColor2: UIColor?
|
||||
@IBInspectable var gradientColor3: UIColor?
|
||||
@IBInspectable var gradientStartPoint: CGPoint = CGPoint.zero
|
||||
@IBInspectable var gradientOffset: CGPoint = CGPoint(x: 0, y: 1)
|
||||
|
||||
//MARK: - Shadow
|
||||
|
||||
@IBInspectable var shadowColor: UIColor = .clear
|
||||
@IBInspectable var shadowOffset: CGSize = CGSize.zero
|
||||
@IBInspectable var shadowRadius: CGFloat = 0.0
|
||||
@IBInspectable var shadowOpacity: CGFloat = 0.0
|
||||
|
||||
//MARK: - Radius
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
|
||||
//MARK: - Border
|
||||
|
||||
@IBInspectable var borderColor: UIColor = .clear
|
||||
@IBInspectable var borderWidth: CGFloat = 0.0
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
|
||||
if let gradientColor = gradientColor {
|
||||
|
||||
var colors: [CGColor] = []
|
||||
colors.append(fillColor.cgColor)
|
||||
colors.append(gradientColor.cgColor)
|
||||
if let gradientColor2 = gradientColor2 { colors.append(gradientColor2.cgColor) }
|
||||
if let gradientColor3 = gradientColor3 { colors.append(gradientColor3.cgColor) }
|
||||
|
||||
let glayer = CAGradientLayer()
|
||||
glayer.frame = bounds
|
||||
glayer.colors = colors
|
||||
glayer.startPoint = gradientStartPoint
|
||||
glayer.endPoint = gradientOffset
|
||||
glayer.cornerRadius = radius()
|
||||
layer.insertSublayer(glayer, at: 0)
|
||||
|
||||
} else {
|
||||
layer.backgroundColor = fillColor.cgColor
|
||||
}
|
||||
|
||||
layer.cornerRadius = radius()
|
||||
|
||||
layer.shadowOffset = shadowOffset
|
||||
layer.shadowOpacity = Float(shadowOpacity / 10.0)
|
||||
layer.shadowRadius = shadowRadius
|
||||
layer.shadowColor = shadowColor.cgColor
|
||||
|
||||
layer.borderColor = borderColor.cgColor
|
||||
layer.borderWidth = borderWidth
|
||||
}
|
||||
|
||||
func radius() -> CGFloat {
|
||||
let minSize = min(width, height)
|
||||
let radius = ((cornerRadius < 0) ? (minSize / 2) : cornerRadius)
|
||||
return radius
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// Device.swift
|
||||
// Inferential Futures
|
||||
//
|
||||
// Created by Valentin on 22.10.2020.
|
||||
// Copyright © 2020 Valentin Titov. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
fileprivate let kIPad = "iPad"
|
||||
fileprivate let kIPhone = "iPhone"
|
||||
|
||||
enum DeviceType {
|
||||
case iPhoneSE, iPhoneMedium, iPhoneDefault, iPhoneMax, iPhoneX, iPhoneXMax, simulator, iPad97, iPad105, iPad129
|
||||
}
|
||||
|
||||
enum DeviceModel:String {
|
||||
case iPodTouch5, iPodTouch6,
|
||||
iPhone4, iPhone4s, iPhone5, iPhone5c, iPhone5s, iPhoneSE, iPhoneSE2ndGen,
|
||||
iPhone6, iPhone6s, iPhone7, iPhone8,
|
||||
iPhone6Plus, iPhone6sPlus, iPhone7Plus, iPhone8Plus, iPhoneX, iPhoneXS, iPhoneXSMax, iPhoneXR, iPhone11, iPhone11Pro, iPhone11ProMax,
|
||||
iPhone12Mini, iPhone12, iPhone12Pro, iPhone12ProMax,
|
||||
iPadMini, iPadMini2, iPadMini3, iPadMini4, iPadMini5,
|
||||
iPad2, iPad3, iPad4, iPadAir, iPadAir2, iPadPro, iPad5, iPadPro10, iPad2018,
|
||||
iPadPro12Inch, iPadPro12Inch2, iPadPro11,iPadPro12Inch3,
|
||||
appleTV, simulator, unknown
|
||||
}
|
||||
|
||||
open class Device {
|
||||
|
||||
// MARK: - Type
|
||||
|
||||
static public let isIpad = UIDevice.current.userInterfaceIdiom == .pad
|
||||
static public let isIphone = UIDevice.current.userInterfaceIdiom == .phone
|
||||
static public let isRetina = UIScreen.main.scale >= 2.0
|
||||
|
||||
class public var isBigIphone: Bool { get { (isIphone && ScreenSize.screenMax > 568.0) } }
|
||||
class public var isIphoneX: Bool { get { (isIphone && ScreenSize.screenMax > 736.0) } }
|
||||
class public var is12ProMax: Bool { get { (isIphone && ScreenSize.screenMax > 926.0) } }
|
||||
class public var SE: Bool { get { !isBigIphone } }
|
||||
|
||||
static public var isSimulator: Bool { return UIDevice.device == .simulator }
|
||||
}
|
||||
|
||||
extension UIDevice {
|
||||
|
||||
// MARK: - Device Model
|
||||
|
||||
static var modelName: DeviceModel {
|
||||
var systemInfo = utsname()
|
||||
uname(&systemInfo)
|
||||
let machineMirror = Mirror(reflecting: systemInfo.machine)
|
||||
let identifier = machineMirror.children.reduce(String.empty) { identifier, element in
|
||||
guard let value = element.value as? Int8 , value != 0 else { return identifier }
|
||||
return identifier + String(UnicodeScalar(UInt8(value)))
|
||||
}
|
||||
|
||||
switch identifier {
|
||||
case "iPod5,1": return .iPodTouch5
|
||||
case "iPod7,1": return .iPodTouch6
|
||||
case "iPhone3,1", "iPhone3,2", "iPhone3,3": return .iPhone4
|
||||
case "iPhone4,1": return .iPhone4s
|
||||
case "iPhone5,1", "iPhone5,2": return .iPhone5
|
||||
case "iPhone5,3", "iPhone5,4": return .iPhone5c
|
||||
case "iPhone6,1", "iPhone6,2": return .iPhone5s
|
||||
case "iPhone7,2": return .iPhone6
|
||||
case "iPhone7,1": return .iPhone6Plus
|
||||
case "iPhone8,1": return .iPhone6s
|
||||
case "iPhone8,2": return .iPhone6sPlus
|
||||
case "iPhone8,4": return .iPhoneSE
|
||||
case "iPhone9,1", "iPhone9,3": return .iPhone7
|
||||
case "iPhone9,2", "iPhone9,4": return .iPhone7Plus
|
||||
case "iPhone10,1", "iPhone10,3": return .iPhone8
|
||||
case "iPhone10,2", "iPhone10,4": return .iPhone8Plus
|
||||
case "iPhone10,5", "iPhone10,6": return .iPhoneX
|
||||
case "iPhone11,2": return .iPhoneXS
|
||||
case "iPhone11,4", "iPhone11,6": return .iPhoneXSMax
|
||||
case "iPhone11,8": return .iPhoneXR
|
||||
case "iPhone12,1": return .iPhone11
|
||||
case "iPhone12,3": return .iPhone11Pro
|
||||
case "iPhone12,5": return .iPhone11ProMax
|
||||
case "iPhone12,8": return .iPhoneSE2ndGen
|
||||
case "iPhone13,1": return .iPhone12Mini
|
||||
case "iPhone13,2": return .iPhone12
|
||||
case "iPhone13,3": return .iPhone12Pro
|
||||
case "iPhone13,4": return .iPhone12ProMax
|
||||
case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return .iPad2
|
||||
case "iPad3,1", "iPad3,2", "iPad3,3": return .iPad3
|
||||
case "iPad3,4", "iPad3,5", "iPad3,6": return .iPad4
|
||||
case "iPad4,1", "iPad4,2", "iPad4,3": return .iPadAir
|
||||
case "iPad5,3", "iPad5,4": return .iPadAir2
|
||||
case "iPad2,5", "iPad2,6", "iPad2,7": return .iPadMini
|
||||
case "iPad4,4", "iPad4,5", "iPad4,6": return .iPadMini2
|
||||
case "iPad4,7", "iPad4,8", "iPad4,9": return .iPadMini3
|
||||
case "iPad5,1", "iPad5,2": return .iPadMini4
|
||||
case "iPad11,1", "iPad11,2": return .iPadMini5
|
||||
case "iPad6,3", "iPad6,4": return .iPadPro
|
||||
case "iPad6,7", "iPad6,8": return .iPadPro12Inch
|
||||
case "iPad6,11","iPad6,12": return .iPad5
|
||||
case "iPad7,1", "iPad7,2": return .iPadPro12Inch2
|
||||
case "iPad7,3", "iPad7,4": return .iPadPro10
|
||||
case "iPad7,5", "iPad7,6": return .iPad2018
|
||||
case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4":return .iPadPro11
|
||||
case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8":return .iPadPro12Inch3
|
||||
case "AppleTV1,1","AppleTV2,1","AppleTV3,1",
|
||||
"AppleTV3,2","AppleTV5,3","AppleTV6,2": return .appleTV
|
||||
case "i386", "x86_64", "arm64": return .simulator
|
||||
default: return .unknown
|
||||
}
|
||||
}
|
||||
|
||||
static var idiom: UIUserInterfaceIdiom {
|
||||
get { return UIDevice.current.userInterfaceIdiom }
|
||||
}
|
||||
|
||||
// MARK: - Device Type
|
||||
|
||||
static var device:DeviceType {
|
||||
switch UIDevice.current.userInterfaceIdiom {
|
||||
case .pad:
|
||||
let screenSize = UIScreen.main.bounds.size
|
||||
let height = max(screenSize.width, screenSize.height)
|
||||
switch height {
|
||||
case 1024: return .iPad97
|
||||
case 1112: return .iPad105
|
||||
case 1366: return .iPad129
|
||||
default: return .iPad97
|
||||
}
|
||||
case .phone:
|
||||
switch modelName {
|
||||
case .iPhone5,.iPhone5c,.iPhone5s,.iPhoneSE: return .iPhoneSE
|
||||
case .iPhone6, .iPhone7, .iPhone8, .iPhoneSE2ndGen, .iPhone12Mini: return .iPhoneMedium
|
||||
case .iPhone6Plus, .iPhone6sPlus, .iPhone7Plus, .iPhone8Plus: return .iPhoneMax
|
||||
case .iPhoneX, .iPhoneXS, .iPhone11Pro, .iPhone12Pro: return .iPhoneX
|
||||
case .iPhoneXR,.iPhoneXSMax, .iPhone11, .iPhone11ProMax, .iPhone12, .iPhone12ProMax: return .iPhoneXMax
|
||||
case .simulator : return .simulator
|
||||
default: return .iPhoneDefault
|
||||
}
|
||||
default: return .iPhoneDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Screen.swift
|
||||
// Realme
|
||||
//
|
||||
// Created by Valentin Titov on 04.01.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// Common screen constants
|
||||
var offset: CGFloat = 16
|
||||
|
||||
var lastNavBarHeight: CGFloat = 0
|
||||
|
||||
struct ScreenSize {
|
||||
static var size:CGSize { get { return UIScreen.main.bounds.size }}
|
||||
static var bounds:CGRect { get { return CGRect(origin: .zero, size: size) }}
|
||||
static var width:CGFloat { get { return size.width }}
|
||||
static var height:CGFloat { get { return size.height }}
|
||||
// static var aspectRatio: CGFloat { get { return size.aspectRatio }}
|
||||
static var screenMax:CGFloat { get { max(width, height) } }
|
||||
static var screenMin:CGFloat { get { min(width, height) } }
|
||||
|
||||
// MARK: - X Padding
|
||||
|
||||
static var isIphoneXTop: CGFloat { get { ( Device.isIphoneX ? 24.0 : 0.0) } }
|
||||
static var isIphoneXBottom: CGFloat { get { ( Device.isIphoneX ? 34.0 : 0.0) } }
|
||||
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
func getStatusBarHeight() -> CGFloat {
|
||||
var statusBarHeight: CGFloat = 0
|
||||
if #available(iOS 13.0, *) {
|
||||
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
|
||||
statusBarHeight = window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
|
||||
} else {
|
||||
statusBarHeight = UIApplication.shared.statusBarFrame.height
|
||||
}
|
||||
return statusBarHeight
|
||||
}
|
||||
|
||||
var topbarHeight: CGFloat {
|
||||
|
||||
let statusHeight = getStatusBarHeight()
|
||||
let navHeight = self.navigationController?.navigationBar.frame.height ?? 0.0
|
||||
|
||||
let height: CGFloat = statusHeight + navHeight
|
||||
lastNavBarHeight = height
|
||||
|
||||
return height
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// UIView+Round.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 07.02.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
//MARK: - Round
|
||||
extension UIView {
|
||||
|
||||
/**
|
||||
Rounds the given set of corners to the specified radius
|
||||
|
||||
- parameter corners: Corners to round
|
||||
- parameter radius: Radius to round to
|
||||
*/
|
||||
func roundRadius(corners: UIRectCorner,
|
||||
radius: CGFloat) {
|
||||
_ = _roundRadius(corners: corners, radius: radius)
|
||||
}
|
||||
|
||||
/**
|
||||
Rounds the given set of corners to the specified radius with a border
|
||||
- parameter corners: Corners to round
|
||||
- parameter radius: Radius to round to
|
||||
- parameter borderColor: The border color
|
||||
- parameter borderWidth: The border width
|
||||
*/
|
||||
func roundRadius(corners: UIRectCorner,
|
||||
radius: CGFloat,
|
||||
borderColor: UIColor,
|
||||
borderWidth: CGFloat) {
|
||||
let mask = _roundRadius(corners: corners, radius: radius)
|
||||
addBorder(mask: mask, borderColor: borderColor, borderWidth: borderWidth)
|
||||
}
|
||||
|
||||
/**
|
||||
Fully rounds an autolayout view (e.g. one with no known frame) with the given diameter and border
|
||||
|
||||
- parameter diameter: The view's diameter
|
||||
- parameter borderColor: The border color
|
||||
- parameter borderWidth: The border width
|
||||
*/
|
||||
func fullyRound(diameter: CGFloat,
|
||||
borderColor: UIColor,
|
||||
borderWidth: CGFloat) {
|
||||
layer.masksToBounds = true
|
||||
layer.cornerRadius = diameter / 2
|
||||
layer.borderWidth = borderWidth
|
||||
layer.borderColor = borderColor.cgColor;
|
||||
}
|
||||
|
||||
@discardableResult func _roundRadius(corners: UIRectCorner,
|
||||
radius: CGFloat) -> CAShapeLayer {
|
||||
let path = UIBezierPath(roundedRect: bounds,
|
||||
byRoundingCorners: corners,
|
||||
cornerRadii: CGSize(width: radius, height: radius))
|
||||
let mask = CAShapeLayer()
|
||||
mask.path = path.cgPath
|
||||
self.layer.mask = mask
|
||||
return mask
|
||||
}
|
||||
|
||||
func addBorder(mask: CAShapeLayer,
|
||||
borderColor: UIColor,
|
||||
borderWidth: CGFloat) {
|
||||
let borderLayer = CAShapeLayer()
|
||||
borderLayer.path = mask.path
|
||||
borderLayer.fillColor = UIColor.clear.cgColor
|
||||
borderLayer.strokeColor = borderColor.cgColor
|
||||
borderLayer.lineWidth = borderWidth
|
||||
borderLayer.frame = bounds
|
||||
layer.addSublayer(borderLayer)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Alpha.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 05.02.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
enum Alpha: CGFloat {
|
||||
case invisible = 0.0
|
||||
case halfTransluent = 0.5
|
||||
case visible = 1.0
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
func setAlpha(_ alpha: Alpha) {
|
||||
self.alpha = alpha.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// AutoresizingMask.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 30.05.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
|
||||
func maskAll() {
|
||||
self.autoresizingMask = [ .flexibleTopMargin,
|
||||
.flexibleBottomMargin,
|
||||
.flexibleLeftMargin,
|
||||
.flexibleRightMargin,
|
||||
.flexibleWidth,
|
||||
.flexibleHeight,]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// Blur.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 21.05.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Blurberry
|
||||
|
||||
protocol AppSceneProtocol {
|
||||
var window: UIWindow? { get set }
|
||||
}
|
||||
|
||||
private let overBlurTag: Int = 9036164
|
||||
|
||||
//MARK: - OverBlurView
|
||||
extension AppSceneProtocol {
|
||||
|
||||
public func appearOverBlurView() {
|
||||
|
||||
let effect = UIBlurEffect(style: .light)
|
||||
let view = UIVisualEffectView(effect: nil)
|
||||
view.blur.radius = 5.0
|
||||
view.blur.tintColor = .clear
|
||||
view.frame = window!.frame
|
||||
view.tag = overBlurTag
|
||||
|
||||
UIView.animate(withDuration: Speed.ms100.rawValue) {
|
||||
view.effect = effect
|
||||
}
|
||||
|
||||
self.window?.addSubview(view)
|
||||
}
|
||||
|
||||
public func dissappearOverBlurView() {
|
||||
|
||||
mainAsync(delay: Speed.ms100.rawValue) {
|
||||
|
||||
guard let view = self.window?.viewWithTag(overBlurTag) as? UIVisualEffectView else { return }
|
||||
|
||||
UIView.animate(withDuration: Speed.ms100.rawValue,
|
||||
animations: {
|
||||
view.effect = nil
|
||||
}, completion: { _ in
|
||||
view.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Bundle.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 02.03.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Bundle {
|
||||
static var appName: String {
|
||||
return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "KvantMobile"
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// BindableGestureRecognizer.swift
|
||||
// ButtonLayersClickStyle
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 09.03.2022.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
final class BindableGestureRecognizer: UITapGestureRecognizer {
|
||||
private var action: (UITapGestureRecognizer) -> Void
|
||||
|
||||
init(action: @escaping (UITapGestureRecognizer) -> Void) {
|
||||
self.action = action
|
||||
super.init(target: nil, action: nil)
|
||||
self.addTarget(self, action: #selector(execute(_:)))
|
||||
}
|
||||
|
||||
@objc private func execute(_ sender: UITapGestureRecognizer) {
|
||||
action(sender)
|
||||
}
|
||||
}
|
||||
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
//
|
||||
// ButtonAnimationView.swift
|
||||
// ButtonLayersClickStyle
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 09.03.2022.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension CGFloat {
|
||||
static func random() -> CGFloat {
|
||||
return CGFloat(arc4random()) / CGFloat(UInt32.max)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIColor {
|
||||
static func random() -> UIColor {
|
||||
return UIColor(
|
||||
red: .random(),
|
||||
green: .random(),
|
||||
blue: .random(),
|
||||
alpha: 1.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@IBDesignable
|
||||
class ButtonAnimationView: UIView {
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat = 0.0
|
||||
@IBInspectable var animationType: Int = 0
|
||||
var animationTypeValue: CGFloat?
|
||||
@IBInspectable var allSubviews: Bool = false
|
||||
@IBInspectable var startClick: Bool = false
|
||||
|
||||
|
||||
var addViews: [UIView]?
|
||||
|
||||
var setupDone = false
|
||||
|
||||
public var type: UIButton.TapType = .alpha(0.5)
|
||||
private var subviewsAnimation = false
|
||||
public var button: UIButton?
|
||||
// public var mainView: UIView?
|
||||
|
||||
|
||||
// override func draw(_ rect: CGRect) {
|
||||
// super.draw(rect)
|
||||
//
|
||||
// updateSubviews()
|
||||
// }
|
||||
|
||||
// MARK: - Initialize
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
// updateSubviews()
|
||||
}
|
||||
|
||||
// public init() {
|
||||
// super.init(frame: .zero)
|
||||
// updateSubviews()
|
||||
// }
|
||||
|
||||
|
||||
|
||||
public init(
|
||||
frame: CGRect,
|
||||
radius: CGFloat = 0.0,
|
||||
animation: Int = 0,
|
||||
value: CGFloat? = nil,
|
||||
addViews: [UIView]? = nil,
|
||||
startClick: Bool = false
|
||||
) {
|
||||
super.init(frame: frame)
|
||||
self.cornerRadius = radius
|
||||
self.animationType = animation
|
||||
self.animationTypeValue = value
|
||||
self.addViews = addViews
|
||||
self.startClick = startClick
|
||||
// updateSubviews()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - layout subViews Update
|
||||
|
||||
override func layoutSubviews() {
|
||||
updateSubviews()
|
||||
}
|
||||
|
||||
public func updateSubviews() {
|
||||
|
||||
if setupDone {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if button == nil {
|
||||
createButton()
|
||||
}
|
||||
// if animationType == 9 {
|
||||
// createMainViewCopy()
|
||||
// }
|
||||
|
||||
if animationType != 0 {
|
||||
|
||||
let type: UIButton.TapType = {
|
||||
switch animationType {
|
||||
case 1: return .alpha(animationTypeValue ?? 0.5)
|
||||
case 2: return .layerGray(animationTypeValue ?? 0.22)
|
||||
case 3: return .pulsate(new: false)
|
||||
case 4: return .pulsate(new: true)
|
||||
case 5: return .shake(new: false)
|
||||
case 6: return .shake(new: true)
|
||||
case 7: return .flash
|
||||
case 8: return .color(value: UIColor.random())
|
||||
case 9: return .androidClickable(dark: false)
|
||||
case 10: return .androidClickable(dark: true)
|
||||
default: return .alpha(0.5)
|
||||
}
|
||||
}()
|
||||
|
||||
setAnimation(type: type)
|
||||
|
||||
if startClick {
|
||||
startClick = false
|
||||
|
||||
let nviews = getViews()
|
||||
button?.onClick(views: nviews, radius: cornerRadius, type: type)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
setupDone = true
|
||||
}
|
||||
|
||||
func getType(value: CGFloat? = nil) -> UIButton.TapType {
|
||||
|
||||
let type: UIButton.TapType = {
|
||||
switch animationType {
|
||||
case 1: return .alpha(value ?? 0.5)
|
||||
case 2: return .layerGray(value ?? 0.22)
|
||||
case 3: return .pulsate(new: false)
|
||||
case 4: return .pulsate(new: true)
|
||||
case 5: return .shake(new: false)
|
||||
case 6: return .shake(new: true)
|
||||
case 7: return .flash
|
||||
case 8: return .color(value: UIColor.random())
|
||||
case 9: return .androidClickable(dark: false)
|
||||
case 10: return .androidClickable(dark: true)
|
||||
default: return .alpha(0.5)
|
||||
}
|
||||
}()
|
||||
return type
|
||||
}
|
||||
|
||||
public func onClick() {
|
||||
button?.onClick(views: getViews(), radius: cornerRadius, type: getType())
|
||||
}
|
||||
|
||||
// public func createMainViewCopy() {
|
||||
// let view = UIView(frame: bounds)
|
||||
// view.backgroundColor = .clear
|
||||
// view.layer.cornerRadius = cornerRadius
|
||||
// view.maskAll()
|
||||
// addSubview(view)
|
||||
// self.mainView = view
|
||||
// }
|
||||
|
||||
public func createButton() {
|
||||
var fr: CGRect = bounds
|
||||
if subviews.count != 0 {
|
||||
let mainView = subviews[0]
|
||||
fr = mainView.frame
|
||||
}
|
||||
|
||||
let btn = UIButton(type: .system)
|
||||
btn.frame = fr
|
||||
btn.backgroundColor = .clear
|
||||
btn.setTitle(nil, for: .normal)
|
||||
|
||||
// btn.contentVerticalAlignment = .center
|
||||
btn.layer.cornerRadius = cornerRadius
|
||||
btn.clipsToBounds = true
|
||||
// btn.maskAll()
|
||||
addSubview(btn)
|
||||
self.button = btn
|
||||
}
|
||||
|
||||
func getViews(views: [UIView]? = nil) -> [UIView] {
|
||||
|
||||
var nviews: [UIView] = []
|
||||
if let views = views {
|
||||
nviews = views
|
||||
} else {
|
||||
if allSubviews {
|
||||
nviews.append(self)
|
||||
subviews.forEach {
|
||||
nviews.append($0)
|
||||
}
|
||||
} else {
|
||||
|
||||
|
||||
|
||||
|
||||
if animationType == 8, let v = button {
|
||||
nviews.append(v)
|
||||
} else if ((animationType == 9) || (animationType == 10)), let v = button {
|
||||
nviews.append(v)
|
||||
} else if subviews.count != 0 {
|
||||
|
||||
if let addViews = addViews {
|
||||
|
||||
nviews = addViews
|
||||
|
||||
// if let tagsID = viewsTagsID {
|
||||
// let tags = tagsID.components(separatedBy: ",")
|
||||
// for str in tags {
|
||||
// let tagNumb = Int(str) ?? 0
|
||||
// for viww in subviews {
|
||||
// if viww.tag == tagNumb {
|
||||
// nviews.append(viww)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
let mainView = subviews[0]
|
||||
nviews.append(mainView)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nviews
|
||||
}
|
||||
|
||||
public func setAnimation(type: UIButton.TapType, views: [UIView]? = nil) {
|
||||
self.type = type
|
||||
// updateSubviews()
|
||||
|
||||
layer.cornerRadius = cornerRadius
|
||||
|
||||
let nviews = getViews(views: views)
|
||||
|
||||
button?.tapHideAnimation(views: nviews, radius: cornerRadius, type: type, callback: { type in
|
||||
if type == .touchUpInside {
|
||||
|
||||
}
|
||||
})
|
||||
subviewsAnimation = true
|
||||
}
|
||||
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// PulseAnimation.swift
|
||||
// ButtonLayersClickStyle
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 09.03.2022.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class PulseAnimation: CALayer {
|
||||
|
||||
var animationGroup = CAAnimationGroup()
|
||||
var animationDuration: TimeInterval = 1.5
|
||||
var radius: CGFloat = 200
|
||||
var numebrOfPulse: Float = Float.infinity
|
||||
|
||||
override init(layer: Any) {
|
||||
super.init(layer: layer)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
init(numberOfPulse: Float = Float.infinity, radius: CGFloat, postion: CGPoint){
|
||||
super.init()
|
||||
self.backgroundColor = UIColor.black.cgColor
|
||||
self.contentsScale = UIScreen.main.scale
|
||||
self.opacity = 0
|
||||
self.radius = radius
|
||||
self.numebrOfPulse = numberOfPulse
|
||||
self.position = postion
|
||||
|
||||
self.bounds = CGRect(x: 0, y: 0, width: radius*2, height: radius*2)
|
||||
self.cornerRadius = radius
|
||||
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
self.setupAnimationGroup()
|
||||
DispatchQueue.main.async {
|
||||
self.add(self.animationGroup, forKey: "pulse")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scaleAnimation() -> CABasicAnimation {
|
||||
let scaleAnimaton = CABasicAnimation(keyPath: "transform.scale.xy")
|
||||
scaleAnimaton.fromValue = NSNumber(value: 0)
|
||||
scaleAnimaton.toValue = NSNumber(value: 1)
|
||||
scaleAnimaton.duration = animationDuration
|
||||
return scaleAnimaton
|
||||
}
|
||||
|
||||
func createOpacityAnimation() -> CAKeyframeAnimation {
|
||||
let opacityAnimiation = CAKeyframeAnimation(keyPath: "opacity")
|
||||
opacityAnimiation.duration = animationDuration
|
||||
opacityAnimiation.values = [0.4,0.8,0]
|
||||
opacityAnimiation.keyTimes = [0,0.3,1]
|
||||
return opacityAnimiation
|
||||
}
|
||||
|
||||
func setupAnimationGroup() {
|
||||
self.animationGroup.duration = animationDuration
|
||||
self.animationGroup.repeatCount = numebrOfPulse
|
||||
let defaultCurve = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default)
|
||||
self.animationGroup.timingFunction = defaultCurve
|
||||
self.animationGroup.animations = [scaleAnimation(),createOpacityAnimation()]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
//
|
||||
// UIButton+AllAnimations.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 20.08.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// MARK: Animations
|
||||
|
||||
extension UIColor {
|
||||
var inverted: UIColor {
|
||||
var r: CGFloat = 0.0, g: CGFloat = 0.0, b: CGFloat = 0.0, a: CGFloat = 0.0
|
||||
self.getRed(&r, green: &g, blue: &b, alpha: &a)
|
||||
return UIColor(red: (1 - r), green: (1 - g), blue: (1 - b), alpha: a) // Assuming you want the same alpha value.
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Button extension
|
||||
|
||||
extension UIButton {
|
||||
|
||||
// MARK: - Alpha
|
||||
|
||||
func updateChangeAlpha(with views: [UIView], visible: Alpha, value: CGFloat, duration: Speed?) {
|
||||
|
||||
var alpha: CGFloat = 1.0
|
||||
if visible != .visible {
|
||||
alpha = value
|
||||
}
|
||||
|
||||
if let duration = duration {
|
||||
UIView.animate(with: duration) {
|
||||
for view in views {
|
||||
view.alpha = alpha
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for view in views {
|
||||
view.alpha = alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Layer Gray
|
||||
|
||||
func updateLayerGray(with views: [UIView], cornRadius: CGFloat? = nil, visible: Alpha, value: CGFloat, duration: Speed?) {
|
||||
|
||||
// let filterViews = views
|
||||
let filterViews = views.filter { !(($0 is UIButton) || ($0 is UILabel)) }
|
||||
|
||||
guard let mainView = filterViews.last else { return }
|
||||
let shTag = 31
|
||||
let spechialColor = UIColor(red: 10.0 / 255.0, green: 13.0 / 255.0, blue: 38.0 / 255.0, alpha: value)
|
||||
|
||||
if visible == .visible {
|
||||
|
||||
for v in mainView.subviews {
|
||||
|
||||
if v.tag == shTag {
|
||||
if let duration = duration {
|
||||
UIView.animate(
|
||||
with: duration,
|
||||
animations: {
|
||||
v.alpha = 0
|
||||
},
|
||||
completion: { fin in
|
||||
v.removeFromSuperview()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
v.alpha = 0
|
||||
v.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if mainView.viewWithTag(shTag) == nil {
|
||||
|
||||
var radius = cornRadius ?? mainView.layer.cornerRadius
|
||||
if radius == 0 {
|
||||
for sv in mainView.subviews {
|
||||
if sv.layer.cornerRadius != 0 {
|
||||
radius = mainView.layer.cornerRadius
|
||||
}
|
||||
if let desFig = mainView as? DesignFigure {
|
||||
radius = desFig.cornerRadius
|
||||
if radius == -1 {
|
||||
let minSize = min(mainView.frame.width, mainView.bounds.height)
|
||||
radius = minSize / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mainView.viewWithTag(shTag) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
let shadowView = UIView(frame: mainView.bounds)
|
||||
shadowView.tag = shTag
|
||||
shadowView.backgroundColor = spechialColor
|
||||
shadowView.layer.cornerRadius = radius
|
||||
mainView.addSubview(shadowView)
|
||||
shadowView.alpha = ((duration == nil) ? 1 : 0)
|
||||
if let duration = duration {
|
||||
if duration == .zero {
|
||||
shadowView.alpha = 1
|
||||
} else {
|
||||
UIView.animate(with: duration) {
|
||||
shadowView.alpha = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - View extension
|
||||
|
||||
extension UIView {
|
||||
|
||||
// MARK: - Flash
|
||||
|
||||
func flash(duration: Speed = .ms200) {
|
||||
|
||||
let flash = CABasicAnimation(keyPath: "opacity")
|
||||
flash.duration = duration.rawValue
|
||||
flash.fromValue = 1
|
||||
flash.toValue = 0.1
|
||||
flash.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
flash.autoreverses = true
|
||||
flash.repeatCount = 3
|
||||
|
||||
layer.add(flash, forKey: nil)
|
||||
}
|
||||
|
||||
|
||||
func inversionColor(duration: Speed = .ms300, cornRadius: CGFloat? = nil, value: UIColor) { // .s1
|
||||
|
||||
let v = UIView(frame: frame)
|
||||
v.backgroundColor = value.inverted
|
||||
v.layer.compositingFilter = "differenceBlendMode"
|
||||
addSubview(v)
|
||||
}
|
||||
|
||||
// MARK: - Change Color
|
||||
|
||||
func animationColor(duration: Speed = .ms300, cornRadius: CGFloat? = nil, value: UIColor) { // .s1
|
||||
|
||||
let color = CABasicAnimation(keyPath: "backgroundColor")
|
||||
let fromColor = backgroundColor ?? .clear
|
||||
color.fromValue = value.cgColor //UIColor.white.cgColor
|
||||
color.toValue = fromColor.cgColor
|
||||
color.duration = duration.rawValue
|
||||
color.beginTime = CACurrentMediaTime() + 0.1
|
||||
color.autoreverses = false
|
||||
|
||||
// if let r = cornRadius {
|
||||
// layer.cornerRadius = r
|
||||
// }
|
||||
|
||||
|
||||
layer.add(color, forKey: "animationColor")
|
||||
}
|
||||
|
||||
// MARK: - Pulsate
|
||||
|
||||
func pulsate(duration: Speed = .ms200) {
|
||||
|
||||
let pulse = CASpringAnimation(keyPath: "transform.scale")
|
||||
pulse.duration = duration.rawValue
|
||||
pulse.fromValue = 1.0
|
||||
pulse.toValue = 0.85
|
||||
pulse.autoreverses = true
|
||||
pulse.repeatCount = 2
|
||||
pulse.initialVelocity = 0.5
|
||||
|
||||
|
||||
layer.add(pulse, forKey: "pulse")
|
||||
}
|
||||
|
||||
// MARK: - Pulsate 2
|
||||
|
||||
func pulsateNew(visible: Alpha) {
|
||||
if visible == .visible {
|
||||
animate(self, transform: .identity)
|
||||
} else {
|
||||
animate(self, transform: CGAffineTransform.identity.scaledBy(x: 0.85, y: 0.85))
|
||||
}
|
||||
}
|
||||
|
||||
private func animate(_ view: UIView, transform: CGAffineTransform) {
|
||||
UIView.animate(withDuration: 0.4,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 0.5,
|
||||
initialSpringVelocity: 3,
|
||||
options: [.curveEaseInOut],
|
||||
animations: {
|
||||
view.transform = transform
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - Shake
|
||||
|
||||
func shake() {
|
||||
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||
animation.duration = 0.6
|
||||
animation.values = [-20.0, 20.0, -20.0, 20.0, -10.0, 10.0, -5.0, 5.0, 0.0 ]
|
||||
layer.add(animation, forKey: "shake")
|
||||
}
|
||||
|
||||
// MARK: - Shake 2
|
||||
|
||||
func shakeNew(duration: Speed = .ms50) {
|
||||
|
||||
let shake = CABasicAnimation(keyPath: "position")
|
||||
shake.duration = duration.rawValue
|
||||
shake.repeatCount = 2
|
||||
shake.autoreverses = true
|
||||
|
||||
let fromPoint = CGPoint(x: center.x - 5, y: center.y)
|
||||
let fromValue = NSValue(cgPoint: fromPoint)
|
||||
|
||||
let toPoint = CGPoint(x: center.x + 5, y: center.y)
|
||||
let toValue = NSValue(cgPoint: toPoint)
|
||||
|
||||
shake.fromValue = fromValue
|
||||
shake.toValue = toValue
|
||||
|
||||
layer.add(shake, forKey: "position")
|
||||
}
|
||||
|
||||
// MARK: - Android Pulse
|
||||
|
||||
func androidPulseAnimation(duration: Speed = .ms1, dark: Bool, value: CGFloat? = nil, position: CGPoint) {
|
||||
|
||||
var color = dark ? #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.2786076018) : #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
|
||||
if let alpha = value {
|
||||
color = color.withAlphaComponent(alpha)
|
||||
}
|
||||
|
||||
let pulse = PulseAnimation(numberOfPulse: 1, radius: 200, postion: position)
|
||||
pulse.animationDuration = duration.rawValue
|
||||
pulse.backgroundColor = color.cgColor// #colorLiteral(red: 0.05282949957, green: 0.5737867104, blue: 1, alpha: 1)
|
||||
layer.insertSublayer(pulse, below: layer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
//
|
||||
// ViewAnimationSelect.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 22.06.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIButton {
|
||||
|
||||
// MARK: Tap Animation Type
|
||||
|
||||
static let setTag = 963
|
||||
|
||||
enum TapType {
|
||||
case alpha(_ value: CGFloat) // 0.5
|
||||
case layerGray(_ value: CGFloat = 0.1845) // 0.1845 add black shadow
|
||||
case pulsate(new: Bool = false)
|
||||
case shake(new: Bool = false)
|
||||
case flash
|
||||
case color(value: UIColor = .red)
|
||||
case androidClickable(dark: Bool = false)
|
||||
}
|
||||
|
||||
// MARK: Set
|
||||
|
||||
func tapHideAnimation(
|
||||
view: UIView?,
|
||||
type: TapType = .alpha(0.0),
|
||||
hideSpeed: Speed? = .ms100,
|
||||
callback: ((UIControl.Event) -> Void)? = nil
|
||||
) {
|
||||
guard let view = view else { return }
|
||||
tapHideAnimation(views: [view], type: type, callback: callback)
|
||||
}
|
||||
|
||||
func tapHideAnimation(
|
||||
views: [UIView],
|
||||
radius: CGFloat? = nil,
|
||||
type: TapType = .alpha(0.0),
|
||||
hideSpeed: Speed? = .ms100,
|
||||
callback: ((UIControl.Event) -> Void)? = nil
|
||||
) {
|
||||
|
||||
switch type {
|
||||
case .androidClickable(let dark): do {
|
||||
guard let mainView = views.last else { return }
|
||||
|
||||
var tapAdded: Bool = false
|
||||
gestureRecognizers?.forEach {
|
||||
if $0 is UITapGestureRecognizer {
|
||||
tapAdded = true
|
||||
}
|
||||
}
|
||||
if !tapAdded {
|
||||
let tap = BindableGestureRecognizer { [weak self] sender in
|
||||
|
||||
let touchPoint = sender.location(in: mainView)
|
||||
|
||||
guard let dur: Speed = self?.defaultDuration(type: .androidClickable(dark: dark)) else { return }
|
||||
self?.androidPulseAnimation(duration: dur, dark: dark, position: touchPoint)
|
||||
|
||||
}
|
||||
mainView.addGestureRecognizer(tap)
|
||||
}
|
||||
}
|
||||
default: break
|
||||
}
|
||||
|
||||
|
||||
// if tag != UIButton.setTag { return }
|
||||
// tag = UIButton.setTag
|
||||
|
||||
tap(for: .touchDown) { [weak self] in
|
||||
callback?(.touchDown)
|
||||
Logs.add(".touchDown")
|
||||
self?.hideAnimation(with: views, radius: radius, type: type, event: .touchDown, duration: hideSpeed)
|
||||
}
|
||||
tap(for: .touchUpInside) { [weak self] in
|
||||
callback?(.touchUpInside)
|
||||
Logs.add(".touchUpInside")
|
||||
self?.hideAnimation(with: views, radius: radius, visible: .visible, type: type, event: .touchUpInside)
|
||||
}
|
||||
tap(for: .touchUpOutside) { [weak self] in
|
||||
callback?(.touchUpOutside)
|
||||
Logs.add(".touchUpOutside")
|
||||
self?.hideAnimation(with: views, radius: radius, visible: .visible, type: type, event: .touchUpOutside)
|
||||
}
|
||||
tap(for: .touchCancel) { [weak self] in
|
||||
callback?(.touchCancel)
|
||||
Logs.add(".touchCancel")
|
||||
self?.hideAnimation(with: views, radius: radius, visible: .visible, type: type, event: .touchCancel)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - onClick
|
||||
|
||||
func onClick(
|
||||
views: [UIView],
|
||||
radius: CGFloat? = nil,
|
||||
type: TapType = .alpha(0.0),
|
||||
hideSpeed: Speed? = .ms100,
|
||||
callback: ((UIControl.Event) -> Void)? = nil
|
||||
) {
|
||||
main(delay: 0.1) { [weak self] in
|
||||
self?.hideAnimation(with: views, radius: radius, type: type, event: .touchDown, duration: hideSpeed)
|
||||
main(delay: 0.35) { [weak self] in
|
||||
self?.hideAnimation(with: views, radius: radius, visible: .visible, type: type, event: .touchUpInside)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@objc func handleTap(_ sender: UITapGestureRecognizer? = nil) {
|
||||
guard let touchPoint = sender?.location(in: superview) else { return }
|
||||
|
||||
let dur: Speed = defaultDuration(type: .androidClickable(dark: false))
|
||||
androidPulseAnimation(duration: dur, dark: false, position: touchPoint)
|
||||
}
|
||||
|
||||
// MARK: - De-Select
|
||||
|
||||
func deselected(_ views: [UIView]) {
|
||||
hideAnimation(
|
||||
with: views,
|
||||
visible: .visible
|
||||
)
|
||||
}
|
||||
|
||||
func defaultDuration(type: TapType) -> Speed {
|
||||
switch type {
|
||||
case .alpha(_), .layerGray(_): return .ms300
|
||||
case .pulsate(_), .flash: return .ms200
|
||||
case .shake(_): return .ms50
|
||||
case .color(_): return .ms300
|
||||
case .androidClickable(_): return .s1
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Animation Filter
|
||||
|
||||
func hideAnimation(
|
||||
with views: [UIView],
|
||||
radius: CGFloat? = nil,
|
||||
visible: Alpha = .invisible,
|
||||
type: TapType = .alpha(0.0),
|
||||
event: UIControl.Event = .touchUpInside,
|
||||
duration: Speed? = nil
|
||||
) {
|
||||
|
||||
let dur: Speed = defaultDuration(type: type)
|
||||
switch type {
|
||||
case .alpha(let value):
|
||||
updateChangeAlpha(with: views, visible: visible, value: value, duration: dur)
|
||||
case .layerGray(let value):
|
||||
if event == .touchDown {
|
||||
updateLayerGray(with: views, cornRadius: radius, visible: .invisible, value: value, duration: nil)
|
||||
} else {
|
||||
updateLayerGray(with: views, cornRadius: radius, visible: .visible, value: value, duration: dur)
|
||||
}
|
||||
default: break
|
||||
}
|
||||
|
||||
views.forEach {
|
||||
switch type {
|
||||
case .flash: $0.flash(duration: dur)
|
||||
case .color(let value):
|
||||
if event == .touchDown
|
||||
,$0 is UIButton
|
||||
// ,!(($0 is UIButton) || ($0 is UILabel))
|
||||
{
|
||||
$0.animationColor(duration: dur, cornRadius: radius, value: value)
|
||||
}
|
||||
case .pulsate(let new):
|
||||
if new { $0.pulsateNew(visible: visible) }
|
||||
else { if event == .touchDown { $0.pulsate(duration: dur) } }
|
||||
case .shake(let new):
|
||||
if new { $0.shake() }
|
||||
else { $0.shakeNew(duration: dur) }
|
||||
// case .androidClickable:
|
||||
// androidPulseAnimation(duration: dur)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension UIControl.Event {
|
||||
|
||||
func strName() -> String {
|
||||
return enevtName(self)
|
||||
}
|
||||
|
||||
func enevtName(_ event: UIControl.Event) -> String {
|
||||
switch event {
|
||||
case .touchDown: return "touch.Down"
|
||||
case .touchCancel: return "touch.Cancel"
|
||||
case .touchUpOutside: return "touch.Up.Outside"
|
||||
case .touchUpInside: return "touch.Up.Inside"
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// CALayer+Animate.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 04.02.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension CALayer {
|
||||
|
||||
public func animate() -> CALayerAnimate {
|
||||
return CALayerAnimate(layer: self)
|
||||
}
|
||||
}
|
||||
|
||||
public class CALayerAnimate {
|
||||
|
||||
private var animations: [String: CAAnimation]
|
||||
private var duration: CFTimeInterval
|
||||
let layer: CALayer
|
||||
|
||||
init(layer: CALayer) {
|
||||
self.animations = [String: CAAnimation]()
|
||||
self.duration = 0.25 // second
|
||||
self.layer = layer
|
||||
}
|
||||
|
||||
public func shadowOpacity(shadowOpacity: Float) -> CALayerAnimate {
|
||||
let key = "shadowOpacity"
|
||||
let animation = CABasicAnimation(keyPath: key)
|
||||
animation.fromValue = layer.shadowOpacity
|
||||
animation.toValue = shadowOpacity
|
||||
animation.isRemovedOnCompletion = false
|
||||
animation.fillMode = .forwards
|
||||
animations[key] = animation
|
||||
return self
|
||||
}
|
||||
|
||||
public func shadowRadius(shadowRadius: CGFloat) -> CALayerAnimate {
|
||||
let key = "shadowRadius"
|
||||
let animation = CABasicAnimation(keyPath: key)
|
||||
animation.fromValue = layer.shadowRadius
|
||||
animation.toValue = shadowRadius
|
||||
animation.isRemovedOnCompletion = false
|
||||
animation.fillMode = CAMediaTimingFillMode.forwards
|
||||
animations[key] = animation
|
||||
return self
|
||||
}
|
||||
|
||||
public func shadowOffsetX(shadowOffsetX: CGFloat) -> CALayerAnimate {
|
||||
let key = "shadowOffset"
|
||||
|
||||
var toValue: CGSize
|
||||
if let currentAnimation = animations[key] as? CABasicAnimation, let currentValue = currentAnimation.toValue {
|
||||
toValue = (currentValue as AnyObject).cgSizeValue
|
||||
} else {
|
||||
toValue = .zero
|
||||
}
|
||||
toValue.width = shadowOffsetX
|
||||
|
||||
let animation = CABasicAnimation(keyPath: key)
|
||||
animation.fromValue = NSValue(cgSize: layer.shadowOffset)
|
||||
animation.toValue = NSValue(cgSize: toValue)
|
||||
animation.isRemovedOnCompletion = false
|
||||
animation.fillMode = CAMediaTimingFillMode.forwards
|
||||
animations[key] = animation
|
||||
return self
|
||||
}
|
||||
|
||||
public func shadowOffsetY(shadowOffsetY: CGFloat) -> CALayerAnimate {
|
||||
let key = "shadowOffset"
|
||||
|
||||
var toValue: CGSize
|
||||
if let currentAnimation = animations[key] as? CABasicAnimation, let currentValue = currentAnimation.toValue {
|
||||
toValue = (currentValue as AnyObject).cgSizeValue
|
||||
} else {
|
||||
toValue = .zero
|
||||
}
|
||||
toValue.height = shadowOffsetY
|
||||
|
||||
let animation = CABasicAnimation(keyPath: key)
|
||||
animation.fromValue = NSValue(cgSize: layer.shadowOffset)
|
||||
animation.toValue = NSValue(cgSize: toValue)
|
||||
animation.isRemovedOnCompletion = false
|
||||
animation.fillMode = CAMediaTimingFillMode.forwards
|
||||
animations[key] = animation
|
||||
return self
|
||||
}
|
||||
|
||||
public func duration(duration: CFTimeInterval) -> CALayerAnimate {
|
||||
self.duration = duration
|
||||
return self
|
||||
}
|
||||
|
||||
public func start() {
|
||||
for (key, animation) in animations {
|
||||
animation.duration = duration
|
||||
layer.removeAnimation(forKey: key)
|
||||
layer.add(animation, forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// CallPhone.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 24.05.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class CallPhone {
|
||||
|
||||
class func call(phoneNumber:String) {
|
||||
let cleanPhoneNumber = phoneNumber.components(separatedBy: CharacterSet.decimalDigits.inverted).joined(separator: "")
|
||||
let urlString:String = "tel://\(cleanPhoneNumber)"
|
||||
if let phoneCallURL = URL(string: urlString) {
|
||||
if (UIApplication.shared.canOpenURL(phoneCallURL)) {
|
||||
UIApplication.shared.open(phoneCallURL, options: [:], completionHandler: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Cell.swift
|
||||
// FigmaConvertXib
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 08.08.2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UITableViewCell {
|
||||
|
||||
func separator(hide: Bool) {
|
||||
separatorInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: hide ? CGFloat(Double.greatestFiniteMagnitude) : 0.0)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Colors.swift
|
||||
// Dieta
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 23.01.2022.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
extension UIColor {
|
||||
|
||||
// 16진수로 컬러값 세팅
|
||||
convenience init(hex: Int, alpha: CGFloat = 1.0) {
|
||||
let newRed = CGFloat((hex >> 16) & 0xff) / 255
|
||||
let newGreen = CGFloat((hex >> 08) & 0xff) / 255
|
||||
let newBlue = CGFloat((hex >> 00) & 0xff) / 255
|
||||
|
||||
self.init(red:newRed, green:newGreen, blue:newBlue, alpha:alpha)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Date.swift
|
||||
// CarLoan
|
||||
//
|
||||
// Created by Valentin Titov on 28/11/2019.
|
||||
// Copyright © 2019 Valentin Titov. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum DateFormatType: String {
|
||||
case shortSlash = "dd/MM/yyyy"
|
||||
case shortDot = "dd.MM.yyyy"
|
||||
case shortHyphen = "yyyy-MM-dd"
|
||||
case utc = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
|
||||
case dayMonth = "d MMMM"
|
||||
}
|
||||
|
||||
extension Date {
|
||||
func formatted(by type: DateFormatType) -> String {
|
||||
return toString(with: type.rawValue)
|
||||
}
|
||||
|
||||
func toString(with dateFormat: String, ru: Bool = false) -> String {
|
||||
let formatter = DateFormatter()
|
||||
if ru { formatter.locale = Locale(identifier: "ru_RU") }
|
||||
formatter.dateFormat = dateFormat
|
||||
return formatter.string(from: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func localDateAsString(with dateFormat: DateFormatType = .shortHyphen) -> String {
|
||||
if let date = localDate(dateFormat: dateFormat) {
|
||||
return date.toString(with: dateFormat.rawValue)
|
||||
}
|
||||
return .empty
|
||||
}
|
||||
|
||||
func localDate(dateFormat: DateFormatType = .shortHyphen) -> Date? {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = dateFormat.rawValue
|
||||
dateFormatter.timeZone = TimeZone.init(abbreviation: "UTC")
|
||||
let date = dateFormatter.date(from: self)
|
||||
return date
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// Device.swift
|
||||
// Inferential Futures
|
||||
//
|
||||
// Created by Valentin on 22.10.2020.
|
||||
// Copyright © 2020 Valentin Titov. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
fileprivate let kIPad = "iPad"
|
||||
fileprivate let kIPhone = "iPhone"
|
||||
|
||||
enum DeviceType {
|
||||
case iPhoneSE, iPhoneMedium, iPhoneDefault, iPhoneMax, iPhoneX, iPhoneXMax, simulator, iPad97, iPad105, iPad129
|
||||
}
|
||||
|
||||
enum DeviceModel:String {
|
||||
case iPodTouch5, iPodTouch6,
|
||||
iPhone4, iPhone4s, iPhone5, iPhone5c, iPhone5s, iPhoneSE, iPhoneSE2ndGen,
|
||||
iPhone6, iPhone6s, iPhone7, iPhone8,
|
||||
iPhone6Plus, iPhone6sPlus, iPhone7Plus, iPhone8Plus, iPhoneX, iPhoneXS, iPhoneXSMax, iPhoneXR, iPhone11, iPhone11Pro, iPhone11ProMax,
|
||||
iPhone12Mini, iPhone12, iPhone12Pro, iPhone12ProMax,
|
||||
iPadMini, iPadMini2, iPadMini3, iPadMini4, iPadMini5,
|
||||
iPad2, iPad3, iPad4, iPadAir, iPadAir2, iPadPro, iPad5, iPadPro10, iPad2018,
|
||||
iPadPro12Inch, iPadPro12Inch2, iPadPro11,iPadPro12Inch3,
|
||||
appleTV, simulator, unknown
|
||||
}
|
||||
|
||||
open class Device {
|
||||
|
||||
// MARK: - Type
|
||||
|
||||
static public let isIpad = UIDevice.current.userInterfaceIdiom == .pad
|
||||
static public let isIphone = UIDevice.current.userInterfaceIdiom == .phone
|
||||
static public let isRetina = UIScreen.main.scale >= 2.0
|
||||
|
||||
class public var isBigIphone: Bool { get { (isIphone && ScreenSize.screenMax > 568.0) } }
|
||||
class public var isIphoneX: Bool { get { (isIphone && ScreenSize.screenMax > 736.0) } }
|
||||
class public var is12ProMax: Bool { get { (isIphone && ScreenSize.screenMax > 926.0) } }
|
||||
class public var SE: Bool { get { !isBigIphone } }
|
||||
|
||||
static public var isSimulator: Bool { return UIDevice.device == .simulator }
|
||||
}
|
||||
|
||||
extension UIDevice {
|
||||
|
||||
// MARK: - Device Model
|
||||
|
||||
static var modelName: DeviceModel {
|
||||
var systemInfo = utsname()
|
||||
uname(&systemInfo)
|
||||
let machineMirror = Mirror(reflecting: systemInfo.machine)
|
||||
let identifier = machineMirror.children.reduce(String.empty) { identifier, element in
|
||||
guard let value = element.value as? Int8 , value != 0 else { return identifier }
|
||||
return identifier + String(UnicodeScalar(UInt8(value)))
|
||||
}
|
||||
|
||||
switch identifier {
|
||||
case "iPod5,1": return .iPodTouch5
|
||||
case "iPod7,1": return .iPodTouch6
|
||||
case "iPhone3,1", "iPhone3,2", "iPhone3,3": return .iPhone4
|
||||
case "iPhone4,1": return .iPhone4s
|
||||
case "iPhone5,1", "iPhone5,2": return .iPhone5
|
||||
case "iPhone5,3", "iPhone5,4": return .iPhone5c
|
||||
case "iPhone6,1", "iPhone6,2": return .iPhone5s
|
||||
case "iPhone7,2": return .iPhone6
|
||||
case "iPhone7,1": return .iPhone6Plus
|
||||
case "iPhone8,1": return .iPhone6s
|
||||
case "iPhone8,2": return .iPhone6sPlus
|
||||
case "iPhone8,4": return .iPhoneSE
|
||||
case "iPhone9,1", "iPhone9,3": return .iPhone7
|
||||
case "iPhone9,2", "iPhone9,4": return .iPhone7Plus
|
||||
case "iPhone10,1", "iPhone10,3": return .iPhone8
|
||||
case "iPhone10,2", "iPhone10,4": return .iPhone8Plus
|
||||
case "iPhone10,5", "iPhone10,6": return .iPhoneX
|
||||
case "iPhone11,2": return .iPhoneXS
|
||||
case "iPhone11,4", "iPhone11,6": return .iPhoneXSMax
|
||||
case "iPhone11,8": return .iPhoneXR
|
||||
case "iPhone12,1": return .iPhone11
|
||||
case "iPhone12,3": return .iPhone11Pro
|
||||
case "iPhone12,5": return .iPhone11ProMax
|
||||
case "iPhone12,8": return .iPhoneSE2ndGen
|
||||
case "iPhone13,1": return .iPhone12Mini
|
||||
case "iPhone13,2": return .iPhone12
|
||||
case "iPhone13,3": return .iPhone12Pro
|
||||
case "iPhone13,4": return .iPhone12ProMax
|
||||
case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return .iPad2
|
||||
case "iPad3,1", "iPad3,2", "iPad3,3": return .iPad3
|
||||
case "iPad3,4", "iPad3,5", "iPad3,6": return .iPad4
|
||||
case "iPad4,1", "iPad4,2", "iPad4,3": return .iPadAir
|
||||
case "iPad5,3", "iPad5,4": return .iPadAir2
|
||||
case "iPad2,5", "iPad2,6", "iPad2,7": return .iPadMini
|
||||
case "iPad4,4", "iPad4,5", "iPad4,6": return .iPadMini2
|
||||
case "iPad4,7", "iPad4,8", "iPad4,9": return .iPadMini3
|
||||
case "iPad5,1", "iPad5,2": return .iPadMini4
|
||||
case "iPad11,1", "iPad11,2": return .iPadMini5
|
||||
case "iPad6,3", "iPad6,4": return .iPadPro
|
||||
case "iPad6,7", "iPad6,8": return .iPadPro12Inch
|
||||
case "iPad6,11","iPad6,12": return .iPad5
|
||||
case "iPad7,1", "iPad7,2": return .iPadPro12Inch2
|
||||
case "iPad7,3", "iPad7,4": return .iPadPro10
|
||||
case "iPad7,5", "iPad7,6": return .iPad2018
|
||||
case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4":return .iPadPro11
|
||||
case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8":return .iPadPro12Inch3
|
||||
case "AppleTV1,1","AppleTV2,1","AppleTV3,1",
|
||||
"AppleTV3,2","AppleTV5,3","AppleTV6,2": return .appleTV
|
||||
case "i386", "x86_64", "arm64": return .simulator
|
||||
default: return .unknown
|
||||
}
|
||||
}
|
||||
|
||||
static var idiom: UIUserInterfaceIdiom {
|
||||
get { return UIDevice.current.userInterfaceIdiom }
|
||||
}
|
||||
|
||||
// MARK: - Device Type
|
||||
|
||||
static var device:DeviceType {
|
||||
switch UIDevice.current.userInterfaceIdiom {
|
||||
case .pad:
|
||||
let screenSize = UIScreen.main.bounds.size
|
||||
let height = max(screenSize.width, screenSize.height)
|
||||
switch height {
|
||||
case 1024: return .iPad97
|
||||
case 1112: return .iPad105
|
||||
case 1366: return .iPad129
|
||||
default: return .iPad97
|
||||
}
|
||||
case .phone:
|
||||
switch modelName {
|
||||
case .iPhone5,.iPhone5c,.iPhone5s,.iPhoneSE: return .iPhoneSE
|
||||
case .iPhone6, .iPhone7, .iPhone8, .iPhoneSE2ndGen, .iPhone12Mini: return .iPhoneMedium
|
||||
case .iPhone6Plus, .iPhone6sPlus, .iPhone7Plus, .iPhone8Plus: return .iPhoneMax
|
||||
case .iPhoneX, .iPhoneXS, .iPhone11Pro, .iPhone12Pro: return .iPhoneX
|
||||
case .iPhoneXR,.iPhoneXSMax, .iPhone11, .iPhone11ProMax, .iPhone12, .iPhone12ProMax: return .iPhoneXMax
|
||||
case .simulator : return .simulator
|
||||
default: return .iPhoneDefault
|
||||
}
|
||||
default: return .iPhoneDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Screen.swift
|
||||
// Realme
|
||||
//
|
||||
// Created by Valentin Titov on 04.01.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// Common screen constants
|
||||
var offset: CGFloat = 16
|
||||
|
||||
var lastNavBarHeight: CGFloat = 0
|
||||
|
||||
struct ScreenSize {
|
||||
static var size:CGSize { get { return UIScreen.main.bounds.size }}
|
||||
static var bounds:CGRect { get { return CGRect(origin: .zero, size: size) }}
|
||||
static var width:CGFloat { get { return size.width }}
|
||||
static var height:CGFloat { get { return size.height }}
|
||||
static var aspectRatio: CGFloat { get { return size.aspectRatio }}
|
||||
static var screenMax:CGFloat { get { max(width, height) } }
|
||||
static var screenMin:CGFloat { get { min(width, height) } }
|
||||
|
||||
// MARK: - X Padding
|
||||
|
||||
static var isIphoneXTop: CGFloat { get { ( Device.isIphoneX ? 24.0 : 0.0) } }
|
||||
static var isIphoneXBottom: CGFloat { get { ( Device.isIphoneX ? 34.0 : 0.0) } }
|
||||
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
func getStatusBarHeight() -> CGFloat {
|
||||
var statusBarHeight: CGFloat = 0
|
||||
if #available(iOS 13.0, *) {
|
||||
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
|
||||
statusBarHeight = window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
|
||||
} else {
|
||||
statusBarHeight = UIApplication.shared.statusBarFrame.height
|
||||
}
|
||||
return statusBarHeight
|
||||
}
|
||||
|
||||
var topbarHeight: CGFloat {
|
||||
|
||||
let statusHeight = getStatusBarHeight()
|
||||
let navHeight = self.navigationController?.navigationBar.frame.height ?? 0.0
|
||||
|
||||
let height: CGFloat = statusHeight + navHeight
|
||||
lastNavBarHeight = height
|
||||
|
||||
return height
|
||||
}
|
||||
}
|
||||
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
//
|
||||
// UIView+Round.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 07.02.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
//MARK: - Round
|
||||
extension UIView {
|
||||
|
||||
func set(x:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.origin.x = x
|
||||
self.frame = frame
|
||||
}
|
||||
|
||||
func set(y:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.origin.y = y
|
||||
self.frame = frame
|
||||
}
|
||||
|
||||
func set(width:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.size.width = width
|
||||
self.frame = frame
|
||||
}
|
||||
|
||||
func set(height:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.size.height = height
|
||||
self.frame = frame
|
||||
}
|
||||
}
|
||||
|
||||
extension CALayer {
|
||||
|
||||
func set(x:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.origin.x = x
|
||||
self.frame = frame
|
||||
}
|
||||
|
||||
func set(y:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.origin.y = y
|
||||
self.frame = frame
|
||||
}
|
||||
|
||||
func set(width:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.size.width = width
|
||||
self.frame = frame
|
||||
}
|
||||
|
||||
func set(height:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.size.height = height
|
||||
self.frame = frame
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIView {
|
||||
|
||||
// MARK: - Basic Properties
|
||||
|
||||
/// X Axis value of UIView.
|
||||
var x: CGFloat {
|
||||
set { self.frame = CGRect(x: _pixelIntegral(newValue),
|
||||
y: self.y,
|
||||
width: self.width,
|
||||
height: self.height)
|
||||
}
|
||||
get { return self.frame.origin.x }
|
||||
}
|
||||
|
||||
/// Y Axis value of UIView.
|
||||
var y: CGFloat {
|
||||
set { self.frame = CGRect(x: self.x,
|
||||
y: _pixelIntegral(newValue),
|
||||
width: self.width,
|
||||
height: self.height)
|
||||
}
|
||||
get { return self.frame.origin.y }
|
||||
}
|
||||
|
||||
/// Width of view.
|
||||
var width: CGFloat {
|
||||
set { self.frame = CGRect(x: self.x,
|
||||
y: self.y,
|
||||
width: _pixelIntegral(newValue),
|
||||
height: self.height)
|
||||
}
|
||||
get { return self.frame.size.width }
|
||||
}
|
||||
|
||||
/// Height of view.
|
||||
var height: CGFloat {
|
||||
set { self.frame = CGRect(x: self.x,
|
||||
y: self.y,
|
||||
width: self.width,
|
||||
height: _pixelIntegral(newValue))
|
||||
}
|
||||
get { return self.frame.size.height }
|
||||
}
|
||||
|
||||
// MARK: - Origin and Size
|
||||
|
||||
/// View's Origin point.
|
||||
var origin: CGPoint {
|
||||
set { self.frame = CGRect(x: _pixelIntegral(newValue.x),
|
||||
y: _pixelIntegral(newValue.y),
|
||||
width: self.width,
|
||||
height: self.height)
|
||||
}
|
||||
get { return self.frame.origin }
|
||||
}
|
||||
|
||||
/// View's size.
|
||||
var size: CGSize {
|
||||
set { self.frame = CGRect(x: self.x,
|
||||
y: self.y,
|
||||
width: _pixelIntegral(newValue.width),
|
||||
height: _pixelIntegral(newValue.height))
|
||||
}
|
||||
get { return self.frame.size }
|
||||
}
|
||||
|
||||
// MARK: - Extra Properties
|
||||
|
||||
/// View's right side (x + width).
|
||||
var right: CGFloat {
|
||||
set { self.x = newValue - self.width }
|
||||
get { return self.x + self.width }
|
||||
}
|
||||
|
||||
/// View's bottom (y + height).
|
||||
var bottom: CGFloat {
|
||||
set { self.y = newValue - self.height }
|
||||
get { return self.y + self.height }
|
||||
}
|
||||
|
||||
/// View's top (y).
|
||||
var top: CGFloat {
|
||||
set { self.y = newValue }
|
||||
get { return self.y }
|
||||
}
|
||||
|
||||
/// View's left side (x).
|
||||
var left: CGFloat {
|
||||
set { self.x = newValue }
|
||||
get { return self.x }
|
||||
}
|
||||
|
||||
/// View's center X value (center.x).
|
||||
var centerX: CGFloat {
|
||||
set { self.center = CGPoint(x: newValue, y: self.centerY) }
|
||||
get { return self.center.x }
|
||||
}
|
||||
|
||||
/// View's center Y value (center.y).
|
||||
var centerY: CGFloat {
|
||||
set { self.center = CGPoint(x: self.centerX, y: newValue) }
|
||||
get { return self.center.y }
|
||||
}
|
||||
|
||||
/// Last subview on X Axis.
|
||||
var lastSubviewOnX: UIView? {
|
||||
return self.subviews.reduce(UIView(frame: .zero)) {
|
||||
return $1.x > $0.x ? $1 : $0
|
||||
}
|
||||
}
|
||||
|
||||
/// Last subview on Y Axis.
|
||||
var lastSubviewOnY: UIView? {
|
||||
return self.subviews.reduce(UIView(frame: .zero)) {
|
||||
return $1.y > $0.y ? $1 : $0
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Bounds Methods
|
||||
|
||||
/// X value of bounds (bounds.origin.x).
|
||||
var boundsX: CGFloat {
|
||||
set { self.bounds = CGRect(x: _pixelIntegral(newValue),
|
||||
y: self.boundsY,
|
||||
width: self.boundsWidth,
|
||||
height: self.boundsHeight)
|
||||
}
|
||||
get { return self.bounds.origin.x }
|
||||
}
|
||||
|
||||
/// Y value of bounds (bounds.origin.y).
|
||||
var boundsY: CGFloat {
|
||||
set { self.frame = CGRect(x: self.boundsX,
|
||||
y: _pixelIntegral(newValue),
|
||||
width: self.boundsWidth,
|
||||
height: self.boundsHeight)
|
||||
}
|
||||
get { return self.bounds.origin.y }
|
||||
}
|
||||
|
||||
/// Width of bounds (bounds.size.width).
|
||||
var boundsWidth: CGFloat {
|
||||
set { self.frame = CGRect(x: self.boundsX,
|
||||
y: self.boundsY,
|
||||
width: _pixelIntegral(newValue),
|
||||
height: self.boundsHeight)
|
||||
}
|
||||
get { return self.bounds.size.width }
|
||||
}
|
||||
|
||||
/// Height of bounds (bounds.size.height).
|
||||
var boundsHeight: CGFloat {
|
||||
set { self.frame = CGRect(x: self.boundsX,
|
||||
y: self.boundsY,
|
||||
width: self.boundsWidth,
|
||||
height: _pixelIntegral(newValue))
|
||||
}
|
||||
get { return self.bounds.size.height }
|
||||
}
|
||||
|
||||
// MARK: - Useful Methods
|
||||
|
||||
/// Center view to it's parent view.
|
||||
// func centerToParent() {
|
||||
// guard let superview = self.superview else { return }
|
||||
//
|
||||
// switch UIApplication.shared.statusBarOrientation {
|
||||
// case .landscapeLeft, .landscapeRight:
|
||||
// self.origin = CGPoint(x: (superview.height / 2) - (self.width / 2),
|
||||
// y: (superview.width / 2) - (self.height / 2))
|
||||
// case .portrait, .portraitUpsideDown:
|
||||
// self.origin = CGPoint(x: (superview.width / 2) - (self.width / 2),
|
||||
// y: (superview.height / 2) - (self.height / 2))
|
||||
// case .unknown:
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
// MARK: - Private Methods
|
||||
fileprivate func _pixelIntegral(_ pointValue: CGFloat) -> CGFloat {
|
||||
let scale = UIScreen.main.scale
|
||||
return (round(pointValue * scale) / scale)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// UIView+Round.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 07.02.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
//MARK: - Round
|
||||
extension UIView {
|
||||
|
||||
/**
|
||||
Rounds the given set of corners to the specified radius
|
||||
|
||||
- parameter corners: Corners to round
|
||||
- parameter radius: Radius to round to
|
||||
*/
|
||||
func roundRadius(corners: UIRectCorner,
|
||||
radius: CGFloat) {
|
||||
_ = _roundRadius(corners: corners, radius: radius)
|
||||
}
|
||||
|
||||
/**
|
||||
Rounds the given set of corners to the specified radius with a border
|
||||
- parameter corners: Corners to round
|
||||
- parameter radius: Radius to round to
|
||||
- parameter borderColor: The border color
|
||||
- parameter borderWidth: The border width
|
||||
*/
|
||||
func roundRadius(corners: UIRectCorner,
|
||||
radius: CGFloat,
|
||||
borderColor: UIColor,
|
||||
borderWidth: CGFloat) {
|
||||
let mask = _roundRadius(corners: corners, radius: radius)
|
||||
addBorder(mask: mask, borderColor: borderColor, borderWidth: borderWidth)
|
||||
}
|
||||
|
||||
/**
|
||||
Fully rounds an autolayout view (e.g. one with no known frame) with the given diameter and border
|
||||
|
||||
- parameter diameter: The view's diameter
|
||||
- parameter borderColor: The border color
|
||||
- parameter borderWidth: The border width
|
||||
*/
|
||||
func fullyRound(diameter: CGFloat,
|
||||
borderColor: UIColor,
|
||||
borderWidth: CGFloat) {
|
||||
layer.masksToBounds = true
|
||||
layer.cornerRadius = diameter / 2
|
||||
layer.borderWidth = borderWidth
|
||||
layer.borderColor = borderColor.cgColor;
|
||||
}
|
||||
|
||||
@discardableResult func _roundRadius(corners: UIRectCorner,
|
||||
radius: CGFloat) -> CAShapeLayer {
|
||||
let path = UIBezierPath(roundedRect: bounds,
|
||||
byRoundingCorners: corners,
|
||||
cornerRadii: CGSize(width: radius, height: radius))
|
||||
let mask = CAShapeLayer()
|
||||
mask.path = path.cgPath
|
||||
self.layer.mask = mask
|
||||
return mask
|
||||
}
|
||||
|
||||
func addBorder(mask: CAShapeLayer,
|
||||
borderColor: UIColor,
|
||||
borderWidth: CGFloat) {
|
||||
let borderLayer = CAShapeLayer()
|
||||
borderLayer.path = mask.path
|
||||
borderLayer.fillColor = UIColor.clear.cgColor
|
||||
borderLayer.strokeColor = borderColor.cgColor
|
||||
borderLayer.lineWidth = borderWidth
|
||||
borderLayer.frame = bounds
|
||||
layer.addSublayer(borderLayer)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
public func background(work: @escaping () -> Void) {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
work()
|
||||
}
|
||||
}
|
||||
|
||||
public func main(work: @escaping () -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
work()
|
||||
}
|
||||
}
|
||||
|
||||
public func mainAsync(delay: Double, work: @escaping () -> Void) {
|
||||
main(delay: delay, work: work)
|
||||
}
|
||||
|
||||
public func main(delay: Double, work: @escaping () -> Void) {
|
||||
let deadline = DispatchTime.now() + delay
|
||||
DispatchQueue.main.asyncAfter(deadline: deadline) {
|
||||
work()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// TextField.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 17.02.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UITextField {
|
||||
@IBInspectable var placeHolderColor: UIColor? {
|
||||
get {
|
||||
return self.placeHolderColor
|
||||
}
|
||||
set {
|
||||
self.attributedPlaceholder = NSAttributedString(string:self.placeholder != nil ? self.placeholder! : "", attributes:[NSAttributedString.Key.foregroundColor: newValue!])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// Jailbreak.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 20.05.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class JailbreakCheck {
|
||||
|
||||
public static func jailbroken() -> Bool {
|
||||
guard let cydiaUrlScheme = URL(string: "cydia://package/com.example.package") else { return isJailbroken() }
|
||||
return UIApplication.shared.canOpenURL(cydiaUrlScheme) || isJailbroken()
|
||||
}
|
||||
|
||||
static func isJailbroken() -> Bool {
|
||||
|
||||
if Device.isSimulator {
|
||||
return false
|
||||
}
|
||||
|
||||
let fileManager = FileManager.default
|
||||
if fileManager.fileExists(atPath: "/Applications/Cydia.app") ||
|
||||
fileManager.fileExists(atPath: "/Library/MobileSubstrate/MobileSubstrate.dylib") ||
|
||||
fileManager.fileExists(atPath: "/bin/bash") ||
|
||||
fileManager.fileExists(atPath: "/usr/sbin/sshd") ||
|
||||
fileManager.fileExists(atPath: "/etc/apt") ||
|
||||
fileManager.fileExists(atPath: "/usr/bin/ssh") {
|
||||
return true
|
||||
}
|
||||
|
||||
if canOpen("/Applications/Cydia.app") ||
|
||||
canOpen("/Library/MobileSubstrate/MobileSubstrate.dylib") ||
|
||||
canOpen("/bin/bash") ||
|
||||
canOpen("/usr/sbin/sshd") ||
|
||||
canOpen("/etc/apt") ||
|
||||
canOpen("/usr/bin/ssh") {
|
||||
return true
|
||||
}
|
||||
|
||||
let path = "/private/" + UUID().uuidString
|
||||
do {
|
||||
try "anyString".write(toFile: path, atomically: true, encoding: .utf8)
|
||||
try fileManager.removeItem(atPath: path)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
static func canOpen(_ path: String) -> Bool {
|
||||
let file = fopen(path, "r")
|
||||
guard file != nil else { return false }
|
||||
fclose(file)
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// LayoutConstraint.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 30.05.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
|
||||
extension NSLayoutConstraint {
|
||||
/**
|
||||
Change multiplier constraint
|
||||
|
||||
- parameter multiplier: CGFloat
|
||||
- returns: NSLayoutConstraint
|
||||
*/
|
||||
func multiplier(_ value: CGFloat) -> NSLayoutConstraint {
|
||||
|
||||
guard let firstItem = firstItem else { return self }
|
||||
|
||||
NSLayoutConstraint.deactivate([self])
|
||||
|
||||
let newConstraint = NSLayoutConstraint(
|
||||
item: firstItem,
|
||||
attribute: firstAttribute,
|
||||
relatedBy: relation,
|
||||
toItem: secondItem,
|
||||
attribute: secondAttribute,
|
||||
multiplier: value,
|
||||
constant: constant
|
||||
)
|
||||
|
||||
newConstraint.priority = priority
|
||||
newConstraint.shouldBeArchived = self.shouldBeArchived
|
||||
newConstraint.identifier = self.identifier
|
||||
|
||||
NSLayoutConstraint.activate([newConstraint])
|
||||
return newConstraint
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// NSData+JsonRu.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 08.06.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Convert Json RU
|
||||
|
||||
func convertJsonRU(to data: Data, encoding: String.Encoding = .utf8) -> String {
|
||||
|
||||
guard let temp: String = String(data: data as Data, encoding: encoding) else { return "" }
|
||||
|
||||
let arr = Array(temp)
|
||||
|
||||
var tab = 0
|
||||
var findLeft = false
|
||||
var findRight = false
|
||||
var findNote = false
|
||||
// var array = false
|
||||
|
||||
var result: [Character] = []
|
||||
let count = arr.count
|
||||
let countMinus = count-1
|
||||
for i in 0..<count {
|
||||
|
||||
let charr = arr[i]
|
||||
var nextChar: Character = " "
|
||||
let y = (i + 1)
|
||||
if y < count {
|
||||
nextChar = arr[y]
|
||||
}
|
||||
|
||||
if (charr != "\"") {
|
||||
|
||||
if ((charr == "{") && (i == 0)) ||
|
||||
((charr == "{") && (nextChar == "\"")) ||
|
||||
((charr == "[") && (nextChar == "\"")) ||
|
||||
((charr == "[") && (nextChar == "{" )) ||
|
||||
((charr == "[") && (nextChar == "]" )) {
|
||||
tab += 1
|
||||
findLeft = true
|
||||
result.append(charr)
|
||||
if (charr == "[") {
|
||||
// array = true
|
||||
result += (" " + "🔢 " + "A" + "r" + "r" + "a" + "y")
|
||||
}
|
||||
}
|
||||
else if ((charr == "}") && (i == countMinus)) ||
|
||||
((charr == "}") && (nextChar == ",")) ||
|
||||
((charr == "}") && (nextChar == "}")) ||
|
||||
((charr == "}") && (nextChar == "]")) ||
|
||||
((charr == "]") && (nextChar == ",")) ||
|
||||
((charr == "]") && (nextChar == "}")) {
|
||||
if 0 < tab {
|
||||
tab -= 1
|
||||
}
|
||||
findRight = true
|
||||
if (charr == "]") {
|
||||
// array = false
|
||||
}
|
||||
}
|
||||
else if (charr == ",") {
|
||||
findNote = true
|
||||
result.append(charr)
|
||||
} else if (charr == ":") {
|
||||
result.append(" ")
|
||||
result.append(charr)
|
||||
result.append(" ")
|
||||
//result.append("👉")
|
||||
// result.append("〰️")
|
||||
// result.append("〰️")
|
||||
// result.append("➰")
|
||||
} else {
|
||||
result.append(charr)
|
||||
}
|
||||
|
||||
if findLeft || findRight || findNote {
|
||||
|
||||
let tabb = " "
|
||||
var t = ""
|
||||
for _ in 0..<tab {
|
||||
t += tabb
|
||||
}
|
||||
|
||||
result.append("🔴")
|
||||
for _ in 0..<t.count {
|
||||
result.append(" ")
|
||||
}
|
||||
// result.append("|")
|
||||
|
||||
findLeft = false
|
||||
findNote = false
|
||||
|
||||
if findRight {
|
||||
result.append(charr)
|
||||
findRight = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var str = String(result)
|
||||
str = str.replacingOccurrences(of: "🔴", with: "\n | ")
|
||||
str = str.replacingOccurrences(of: "false", with: " ❌")
|
||||
str = str.replacingOccurrences(of: "true", with: " ✅")
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Bool.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 03.03.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
extension Bool {
|
||||
static func from(string: String) -> Bool {
|
||||
if string == "true" { return true }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
extension Bool: Stringable {
|
||||
var stringValue: String {
|
||||
if self { return "true" }
|
||||
return "false"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Decimal.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 03.03.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Decimal {
|
||||
var intValue: Int {
|
||||
return (self as NSDecimalNumber).intValue
|
||||
}
|
||||
|
||||
var doubleValue: Double {
|
||||
return (self as NSDecimalNumber).doubleValue
|
||||
}
|
||||
|
||||
func formatWith(currency: CurrencyType) -> String {
|
||||
currencyFormatted(currency)
|
||||
}
|
||||
}
|
||||
|
||||
extension Decimal {
|
||||
|
||||
func currencyFormatted(_ currency: CurrencyType) -> String {
|
||||
let value = self * 100.0
|
||||
let floatValue = (value as NSDecimalNumber).floatValue
|
||||
let intValue = Int(floatValue)
|
||||
var stringValue: String = String(intValue)
|
||||
while stringValue.count < 3 {
|
||||
stringValue.insert("0", at: stringValue.startIndex)
|
||||
}
|
||||
var result: String = currency.symbol
|
||||
result.insert(" ", at: result.startIndex)
|
||||
var cents: String = String(stringValue.removeLast())
|
||||
cents.insert(stringValue.removeLast(), at: cents.startIndex)
|
||||
result = cents + result
|
||||
result.insert(",", at: result.startIndex)
|
||||
let characters: [Character] = Array(stringValue) as [Character]
|
||||
var j = 1
|
||||
for i in (0..<characters.count).reversed() {
|
||||
let character = characters[i]
|
||||
result.insert(character, at: result.startIndex)
|
||||
if j%3 == 0, i != 0 {result.insert(" ", at: result.startIndex)}
|
||||
j += 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
var decimalValue: Decimal? {
|
||||
let formattedStr =
|
||||
replacingOccurrences(of: " ", with: "")
|
||||
.replacingOccurrences(of: ",", with: ".")
|
||||
return Decimal(string: formattedStr)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Double.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 03.03.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Double {
|
||||
func decimalValue() -> Decimal? {
|
||||
return Decimal(self)
|
||||
}
|
||||
|
||||
func currencyFormatted(_ currency: CurrencyType) -> String {
|
||||
let intValue = Int(self * 100.0)
|
||||
var stringValue: String = String(intValue)
|
||||
while stringValue.count < 3 {
|
||||
stringValue.insert("0", at: stringValue.startIndex)
|
||||
}
|
||||
var result: String = currency.symbol
|
||||
result.insert(" ", at: result.startIndex)
|
||||
var cents: String = String(stringValue.removeLast())
|
||||
cents.insert(stringValue.removeLast(), at: cents.startIndex)
|
||||
result = cents + result
|
||||
result.insert(",", at: result.startIndex)
|
||||
let characters: [Character] = Array(stringValue) as [Character]
|
||||
var j = 1
|
||||
for i in (0..<characters.count).reversed() {
|
||||
let character = characters[i]
|
||||
result.insert(character, at: result.startIndex)
|
||||
if j%3 == 0, i != 0 {result.insert(" ", at: result.startIndex)}
|
||||
j += 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Int.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 03.03.2021.
|
||||
//
|
||||
|
||||
import CoreGraphics
|
||||
|
||||
extension Int {
|
||||
static var zero: Int { get {return 0 }}
|
||||
mutating func next() { self += 1 }
|
||||
mutating func increase() { self += 1 }
|
||||
mutating func previous() { self -= 1 }
|
||||
mutating func decrease() { self -= 1 }
|
||||
|
||||
func percentToCGFloat() -> CGFloat {
|
||||
return CGFloat(self)/CGFloat(100.0)
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt8 {
|
||||
public init(_ boolean: BooleanLiteralType) {
|
||||
self = boolean ? 1 : 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Number.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 03.02.2021.
|
||||
//
|
||||
|
||||
import CoreGraphics
|
||||
|
||||
extension CGFloat {
|
||||
static var zero: CGFloat { get {return 0.0 }}
|
||||
var half:CGFloat { get { return self/2 } }
|
||||
var double:CGFloat { get { return self*2 } }
|
||||
var toPercent: CGFloat { get { return self*100.0 } }
|
||||
var syncSize: CGFloat { get { return self * sizeAspect.rawValue }}
|
||||
var syncFont: CGFloat { get { return self * fontAspect.rawValue }}
|
||||
|
||||
//Pi Constants
|
||||
static let π = CGFloat(Double.pi)
|
||||
static let π2 = CGFloat(2 * Double.pi)
|
||||
static let π_2 = CGFloat(Double.pi/2)
|
||||
|
||||
var radians: CGFloat {
|
||||
return (self*CGFloat.pi)/180.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// ScrollView.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 07.04.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIScrollView {
|
||||
|
||||
public func scrollingBottom(animated: Bool = true) {
|
||||
let deadline = DispatchTime.now() + 0.5
|
||||
DispatchQueue.main.asyncAfter(deadline: deadline) {
|
||||
let bottomOffset = CGPoint(x: 0, y: self.contentSize.height - self.bounds.height + self.contentInset.bottom)
|
||||
self.setContentOffset(bottomOffset, animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UITextView {
|
||||
|
||||
func scrollTextToBottom() {
|
||||
if text.count != 0 {
|
||||
let a = (text.count - 1)
|
||||
let bottom: NSRange = NSRange(location: a, length: 1) // a..<1
|
||||
scrollRangeToVisible(bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// SendMail.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 08.06.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MessageUI
|
||||
import SwiftLog
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
func sendEmailLogs() {
|
||||
|
||||
if MFMailComposeViewController.canSendMail() {
|
||||
let composeVC = MFMailComposeViewController()
|
||||
composeVC.mailComposeDelegate = self
|
||||
|
||||
// Configure the fields of the interface.
|
||||
composeVC.setToRecipients(["rustamburger@gmail.com"])
|
||||
composeVC.setSubject("Send Email Logs")
|
||||
composeVC.setMessageBody("", isHTML: false)
|
||||
|
||||
var attachmentData: NSData?
|
||||
|
||||
let path = Log.logger.currentPath
|
||||
if FileManager.default.fileExists(atPath: path) {
|
||||
if let data = NSData(contentsOfFile: path) {
|
||||
attachmentData = data
|
||||
}
|
||||
}
|
||||
|
||||
guard let attData = attachmentData as Data? else { return }
|
||||
composeVC.addAttachmentData(attData, mimeType: "text/plain", fileName: "crash.log")
|
||||
self.present(composeVC, animated: true, completion: nil)
|
||||
|
||||
} else {
|
||||
// Tell user about not able to send email directly.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController: MFMailComposeViewControllerDelegate {
|
||||
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
controller.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Sequances.swift
|
||||
// PB Dev
|
||||
//
|
||||
// Created by Valentin on 04.03.2020.
|
||||
// Copyright © 2020 Plus Bank. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Dictionary {
|
||||
mutating func merge(dict: [Key: Value]){
|
||||
for (k, v) in dict {
|
||||
updateValue(v, forKey: k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence where Element: Numeric {
|
||||
/// Returns the sum of all elements in the collection
|
||||
func sum() -> Element { return reduce(0, +) }
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
//
|
||||
// String.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 02.02.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol Stringable {
|
||||
var stringValue: String { get }
|
||||
}
|
||||
|
||||
protocol OptionalStringable {
|
||||
var stringValue: String? { get }
|
||||
}
|
||||
|
||||
//MARK: - Constants
|
||||
extension String {
|
||||
static let empty: String = ""
|
||||
static let whitespace: String = " "
|
||||
static let slash: String = "/"
|
||||
static let newLine: String = "\n"
|
||||
static let endLine: String = "\r\n"
|
||||
static let colon: String = ":"
|
||||
static let semicolon: String = ";"
|
||||
static let equal: String = "="
|
||||
static let and: String = "&"
|
||||
}
|
||||
|
||||
extension String {
|
||||
func substringInRange(_ range: NSRange) -> String {
|
||||
let range =
|
||||
index(startIndex, offsetBy: range.location)
|
||||
..<
|
||||
index(startIndex, offsetBy: range.location + range.length)
|
||||
return String(self[range])
|
||||
}
|
||||
|
||||
func replaceStringInRange(
|
||||
_ range: NSRange,
|
||||
with string: String
|
||||
) -> String {
|
||||
return (self as NSString)
|
||||
.replacingCharacters(in: range, with: string)
|
||||
.trimmingCharacters(in: .whitespaces) as String
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func index(from: Int) -> Index {
|
||||
return self.index(startIndex, offsetBy: from)
|
||||
}
|
||||
|
||||
func substring(from: Int) -> String {
|
||||
let fromIndex = index(from: from)
|
||||
return String(self[fromIndex...])
|
||||
}
|
||||
|
||||
func substring(to: Int) -> String {
|
||||
let toIndex = index(from: to)
|
||||
return String(self[..<toIndex])
|
||||
}
|
||||
|
||||
func substring(with r: Range<Int>) -> String {
|
||||
let startIndex = index(from: r.lowerBound)
|
||||
let endIndex = index(from: r.upperBound)
|
||||
return String(self[startIndex..<endIndex])
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
subscript (bounds: CountableClosedRange<Int>) -> String {
|
||||
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||
return String(self[start...end])
|
||||
}
|
||||
|
||||
subscript (bounds: CountableRange<Int>) -> String {
|
||||
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||
return String(self[start..<end])
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Letters formatting
|
||||
extension String {
|
||||
var firstCapitalized: String {
|
||||
prefix(1).capitalized + dropFirst()
|
||||
}
|
||||
|
||||
var firstLowercased: String {
|
||||
prefix(1).lowercased() + dropFirst()
|
||||
}
|
||||
|
||||
var positiveStringNumber: String {
|
||||
return "+ \(self)"
|
||||
}
|
||||
|
||||
var negativeStringNumber: String {
|
||||
return "- \(self)"
|
||||
}
|
||||
|
||||
var titleCase: String {
|
||||
return self
|
||||
.replacingOccurrences(of: "([A-Z])",
|
||||
with: " $1",
|
||||
options: .regularExpression,
|
||||
range: range(of: self))
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.capitalized
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
var normalizePhone: String {
|
||||
let decimalPhone = self.decimalString() as String
|
||||
let formatted: String
|
||||
if let symb = decimalPhone.first, (symb == "7" || symb == "8") {
|
||||
formatted = String(decimalPhone.dropFirst())
|
||||
} else {
|
||||
formatted = decimalPhone
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
func decimalString() -> NSString {
|
||||
return components(separatedBy: NSCharacterSet.decimalDigits.inverted).joined() as NSString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension String {
|
||||
|
||||
func addHighlight(texts: [String],
|
||||
color: UIColor = .systemBlue) -> NSMutableAttributedString {
|
||||
|
||||
let attr = NSMutableAttributedString(string: self, attributes: nil)
|
||||
|
||||
for text in texts {
|
||||
let range = (attr.string as NSString).range(of: text)
|
||||
attr.setAttributes([.foregroundColor: color], range: range)
|
||||
}
|
||||
|
||||
return attr
|
||||
}
|
||||
|
||||
func addUnderline() -> NSMutableAttributedString {
|
||||
|
||||
let textRange = NSRange(location: 0, length: self.count)
|
||||
let attr = NSMutableAttributedString(string: self)
|
||||
attr.addAttribute(.underlineStyle,
|
||||
value: NSUnderlineStyle.single.rawValue,
|
||||
range: textRange)
|
||||
|
||||
return attr
|
||||
}
|
||||
|
||||
func remove(currency: CurrencyType) -> String {
|
||||
let value = self
|
||||
.replacingOccurrences(of: currency.symbol, with: "")
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
return value
|
||||
}
|
||||
|
||||
func formattingRemoveSpaces() -> String {
|
||||
return removeSpaces().findReplace(find: " ", replace: "", all: true)
|
||||
}
|
||||
|
||||
func currencyInputFormatting() -> String {
|
||||
|
||||
func formatting(text: String, digits: Int) -> String {
|
||||
let formatting =
|
||||
text.currencyInputFullFormatting(digits: digits, spaces: false)
|
||||
.remove(currency: .rub)
|
||||
.removeSpaces()
|
||||
return formatting
|
||||
}
|
||||
|
||||
let removeSpace = self.formattingRemoveSpaces()
|
||||
|
||||
let needle: Character = ","
|
||||
if let idx = removeSpace.firstIndex(of: needle) {
|
||||
let pos = removeSpace.distance(from: removeSpace.startIndex, to: idx)
|
||||
|
||||
|
||||
let result1 = removeSpace.components(separatedBy: ",")
|
||||
|
||||
if result1.count > 2 {
|
||||
|
||||
let result = String(removeSpace.dropLast())
|
||||
return result
|
||||
|
||||
} else if result1.count > 1 {
|
||||
|
||||
let components = result1[1]
|
||||
print("components \(components.count) ")
|
||||
|
||||
if components.count != 0 {
|
||||
|
||||
var digits = 0
|
||||
let digitsMax = 2
|
||||
if components.count > digitsMax {
|
||||
let newComponents = String(components.prefix(2))
|
||||
let text = result1[0] + newComponents
|
||||
|
||||
return formatting(text: text, digits: digitsMax)
|
||||
}
|
||||
else if digitsMax >= components.count {
|
||||
digits = components.count
|
||||
}
|
||||
|
||||
let text = result1[0] + components
|
||||
return formatting(text: text, digits: digits)
|
||||
} else {
|
||||
return removeSpace // formatting(text: removeSpace, digits: 0)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
print("Found \(needle) at position \(pos) -- \(result1.count) ")
|
||||
|
||||
return removeSpace // formatting(text: removeSpace, digits: 0)
|
||||
}
|
||||
else {
|
||||
print("Not found")
|
||||
return removeSpace
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func currencyInputFullFormatting(digits: Int, spaces: Bool = true) -> String {
|
||||
|
||||
var number: NSNumber!
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .currencyAccounting
|
||||
formatter.currencySymbol = "₽"
|
||||
formatter.maximumFractionDigits = digits
|
||||
formatter.minimumFractionDigits = digits
|
||||
|
||||
var amountWithPrefix = self
|
||||
|
||||
// remove from String: "$", ".", ","
|
||||
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
|
||||
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.count), withTemplate: "")
|
||||
|
||||
var value: Double = 100
|
||||
if digits == 1 {
|
||||
value = 10
|
||||
} else if digits == 0 {
|
||||
value = 1
|
||||
}
|
||||
|
||||
let double = (amountWithPrefix as NSString).doubleValue
|
||||
number = NSNumber(value: (double / value))
|
||||
|
||||
// if first number is 0 or all numbers were deleted
|
||||
guard number != 0 as NSNumber else {
|
||||
return ""
|
||||
}
|
||||
|
||||
let result = formatter.string(from: number)!
|
||||
if !spaces {
|
||||
return result.formattingRemoveSpaces()
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
func find(_ find: String) -> Bool {
|
||||
range(of: find) != nil
|
||||
}
|
||||
|
||||
func indexInt(of char: Character, last: Bool = false) -> (index: Index, number: Int)? {
|
||||
if last {
|
||||
guard let index = lastIndex(of: char) else { return nil }
|
||||
return ( index, index.utf16Offset(in: self) )
|
||||
} else {
|
||||
guard let index = firstIndex(of: char) else { return nil }
|
||||
return ( index, index.utf16Offset(in: self) )
|
||||
}
|
||||
}
|
||||
|
||||
func findReplace(find: String, replace: String, all: Bool = false) -> String {
|
||||
if all {
|
||||
return replacingOccurrences(of: find, with: replace, options: .literal, range: nil)
|
||||
} else {
|
||||
return replacingOccurrences(of: find, with: replace)
|
||||
}
|
||||
}
|
||||
|
||||
func removeSpaces() -> String {
|
||||
trimmingCharacters(in: .whitespaces)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension String {
|
||||
func removingEmoji() -> String {
|
||||
return String(self.filter { !$0.isEmoji() })
|
||||
}
|
||||
func removeEmoji() -> String {
|
||||
return self.components(separatedBy: CharacterSet.symbols).joined()
|
||||
}
|
||||
}
|
||||
|
||||
extension Character {
|
||||
fileprivate func isEmoji() -> Bool {
|
||||
return
|
||||
Character(UnicodeScalar(UInt32(0x1d000))!) <= self && self <= Character(UnicodeScalar(UInt32(0x1f77f))!) ||
|
||||
Character(UnicodeScalar(UInt32(0x2100))!) <= self && self <= Character(UnicodeScalar(UInt32(0x26ff))!) ||
|
||||
|
||||
Character(UnicodeScalar(UInt32(0x1f600))!) <= self && self <= Character(UnicodeScalar(UInt32(0x1f64f))!) ||
|
||||
Character(UnicodeScalar(UInt32(0x1f300))!) <= self && self <= Character(UnicodeScalar(UInt32(0x1f5ff))!) ||
|
||||
Character(UnicodeScalar(UInt32(0x1f680))!) <= self && self <= Character(UnicodeScalar(UInt32(0x1f5ff))!) ||
|
||||
Character(UnicodeScalar(UInt32(0x2600))!) <= self && self <= Character(UnicodeScalar(UInt32(0x2600))!) ||
|
||||
Character(UnicodeScalar(UInt32(0x2700))!) <= self && self <= Character(UnicodeScalar(UInt32(0x27bf))!) ||
|
||||
Character(UnicodeScalar(UInt32(0xfe00))!) <= self && self <= Character(UnicodeScalar(UInt32(0xfe0f))!)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Name Of Class
|
||||
|
||||
extension NSObject {
|
||||
class var nameClss: String{
|
||||
return NSStringFromClass(self).components(separatedBy: ".").last!
|
||||
}
|
||||
var nameClss: String{
|
||||
return NSStringFromClass(type(of: self)).components(separatedBy: ".").last!
|
||||
}
|
||||
var nameObj: String{
|
||||
return String(describing: type(of: Self.self))
|
||||
}
|
||||
func fontSize(se: Bool = false) -> UIFont {
|
||||
return UIFont.systemFont(ofSize: se && Device.SE ? 16 : 18)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// TimeInterval.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 02.02.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
typealias Interval = TimeInterval
|
||||
|
||||
enum Speed: Interval {
|
||||
case zero = 0.0
|
||||
case ms1 = 0.001
|
||||
case ms50 = 0.05
|
||||
case ms100 = 0.1
|
||||
case ms200 = 0.2
|
||||
case ms300 = 0.3
|
||||
case ms350 = 0.35
|
||||
case ms400 = 0.4
|
||||
case ms450 = 0.45
|
||||
case ms500 = 0.5
|
||||
case s1 = 1.0
|
||||
case s2 = 2.0
|
||||
case s3 = 3.0
|
||||
case s4 = 4.0
|
||||
case s5 = 5.0
|
||||
}
|
||||
|
||||
enum Delay: Interval {
|
||||
case ms1 = 0.001
|
||||
case ms100 = 0.1
|
||||
case ms200 = 0.2
|
||||
case ms300 = 0.3
|
||||
case ms400 = 0.4
|
||||
case ms500 = 0.5
|
||||
case s1 = 1.0
|
||||
case s2 = 2.0
|
||||
case s3 = 3.0
|
||||
case s4 = 4.0
|
||||
case s5 = 5.0
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// UIView+AddShadow.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 08.05.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
|
||||
func addShadowFigma(blur: CGFloat, offset: CGSize, color: UIColor, bColor: UIColor = UIColor.background, radius: CGFloat, alpha: Float = 1.0) {
|
||||
|
||||
let layer = self.layer
|
||||
layer.frame = bounds
|
||||
layer.backgroundColor = bColor.cgColor
|
||||
layer.shadowOpacity = alpha
|
||||
layer.shadowColor = color.cgColor
|
||||
layer.shadowOffset = offset
|
||||
layer.shadowRadius = (blur / 2)
|
||||
layer.cornerRadius = radius
|
||||
|
||||
layer.shouldRasterize = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// UIView+Animation.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 28.02.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
func shakeAnimation(with duration: TimeInterval = 0.03,
|
||||
repeates: Float = 3.0,
|
||||
delta: CGFloat = 5.0) {
|
||||
let animationKey: String = "position"
|
||||
let animation = CABasicAnimation(keyPath: animationKey)
|
||||
animation.duration = duration
|
||||
animation.repeatCount = repeates
|
||||
animation.autoreverses = true
|
||||
animation.fromValue = NSValue(cgPoint: CGPoint(x: center.x - delta, y: center.y))
|
||||
animation.toValue = NSValue(cgPoint: CGPoint(x: center.x + delta, y: center.y))
|
||||
layer.add(animation,
|
||||
forKey: animationKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// URL.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 19.04.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension URL {
|
||||
subscript(queryParam:String) -> String? {
|
||||
guard let url = URLComponents(string: self.absoluteString) else { return nil }
|
||||
return url.queryItems?.first(where: { $0.name == queryParam })?.value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Window+Get.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 25.05.2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension UIWindow {
|
||||
|
||||
static var current: UIWindow? {
|
||||
if #available(iOS 13.0, *) {
|
||||
return UIApplication.shared.windows.filter {$0.isKeyWindow}.first
|
||||
} else {
|
||||
return UIApplication.shared.keyWindow
|
||||
}
|
||||
}
|
||||
|
||||
static var root: UIViewController? {
|
||||
UIWindow.current?.rootViewController
|
||||
}
|
||||
|
||||
static var nav: UINavigationController? {
|
||||
if let nav = root as? UINavigationController {
|
||||
return nav
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
static var top: UIViewController? {
|
||||
if let nav = UIWindow.nav {
|
||||
if let vc = nav.topViewController {
|
||||
return vc
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// StoryboardController.swift
|
||||
// PatternsSwift
|
||||
//
|
||||
// Created by mrustaa on 20/04/2020.
|
||||
// Copyright © 2020 mrustaa. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import ContainerControllerSwift
|
||||
|
||||
@IBDesignable
|
||||
class StoryboardController: UIViewController {
|
||||
|
||||
var navBarHide: Bool = false
|
||||
|
||||
class func instantiate() -> UIViewController {
|
||||
return fromStoryboardController()
|
||||
}
|
||||
|
||||
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if navBarHide {
|
||||
navigationController?.navigationBar.isTranslucent = false
|
||||
setNeedsStatusBarAppearanceUpdate()
|
||||
navBar(hide: true)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
if navBarHide {
|
||||
navBar(hide: false)
|
||||
}
|
||||
}
|
||||
|
||||
func navBar(hide: Bool) {
|
||||
self.navigationController?.setNavigationBarHidden(hide, animated: true)
|
||||
}
|
||||
|
||||
class func fromStoryboardController() -> UIViewController {
|
||||
let className = String(describing: self)
|
||||
|
||||
let storyboard = UIStoryboard.init(name: className, bundle: nil)
|
||||
|
||||
// if let initialViewController = storyboard.instantiateInitialViewController() {
|
||||
// return initialViewController
|
||||
// } else {
|
||||
// fatalError("Can't initialize view controller \(self)")
|
||||
// }
|
||||
|
||||
return storyboard.instantiateViewController(withIdentifier: className)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
|
||||
func back() {
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension StoryboardController: ContainerControllerDelegate {
|
||||
|
||||
|
||||
func addContainer(position: ContainerPosition, radius: CGFloat, items: [TableAdapterItem]? = nil, delegate: ContainerControllerDelegate? = nil, addShadow: Bool = true, addBackShadow: Bool = false, header: UIView? = nil, footer: UIView? = nil, back: UIView? = nil) -> (ContainerController, TableAdapterView) {
|
||||
|
||||
let layoutC = ContainerLayout()
|
||||
layoutC.positions = position //
|
||||
layoutC.insets = .init(right: 0, left: 0)
|
||||
let container = ContainerController(addTo: self, layout: layoutC)
|
||||
container.view.cornerRadius = radius
|
||||
container.view.addShadow()
|
||||
container.view.tag = 13
|
||||
if let delegate = delegate {
|
||||
container.delegate = delegate
|
||||
} else {
|
||||
container.delegate = self
|
||||
}
|
||||
|
||||
if addShadow {
|
||||
// let shadowColor = UIColor.black.withAlphaComponent(0.1).cgColor
|
||||
|
||||
container.view.layer.shadowOpacity = Float(0.20)
|
||||
container.view.layer.shadowOffset = .init(width: 0, height: 13)
|
||||
container.view.layer.shadowRadius = 30.0
|
||||
container.view.layer.shadowColor = UIColor.black.cgColor
|
||||
|
||||
|
||||
}
|
||||
|
||||
container.set(backgroundShadowShow: addBackShadow)
|
||||
|
||||
container.view.backgroundColor = .white
|
||||
|
||||
|
||||
// container.view.backgroundColor = color
|
||||
|
||||
if let headerV = header {
|
||||
container.add(headerView: headerV)
|
||||
}
|
||||
|
||||
if let footerV = footer {
|
||||
container.add(footerView: footerV)
|
||||
}
|
||||
|
||||
if let back = back {
|
||||
container.backView?.addSubview(back)
|
||||
}
|
||||
|
||||
|
||||
let table = TableAdapterView(frame: CGRect(x: 0, y: 0, width: ContainerDevice.width, height: 0), style: .plain)
|
||||
table.indicatorStyle = .default
|
||||
// container.add(scrollView: addCollectionView())
|
||||
|
||||
if let items = items {
|
||||
table.set(items: items )
|
||||
|
||||
container.add(scrollView: table)
|
||||
}
|
||||
|
||||
container.move(type: .hide, animation: false)
|
||||
|
||||
// main(delay: 0.05) {
|
||||
// container.move(type: .top
|
||||
// )
|
||||
// }
|
||||
|
||||
return (container, table)
|
||||
}
|
||||
|
||||
|
||||
func containerControllerMove(_ containerController: ContainerController, position: CGFloat, type: ContainerMoveType, animation: Bool) {
|
||||
if animation {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func containerControllerHandlePan(_ containerController: ContainerController, pan: UIPanGestureRecognizer) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// XibView.swift
|
||||
// PatternsSwift
|
||||
//
|
||||
// Created by mrustaa on 19/04/2020.
|
||||
// Copyright © 2020 mrustaa. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class XibView: UIView {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public weak var contentView: UIView?
|
||||
|
||||
// MARK: - Initialize
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
contentView = fromNib()
|
||||
loadedFromNib()
|
||||
}
|
||||
|
||||
public init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
contentView = fromNib()
|
||||
frame = contentView?.frame ?? frame
|
||||
loadedFromNib()
|
||||
}
|
||||
|
||||
// MARK: - Postflight
|
||||
|
||||
open func loadedFromNib() {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Load Nib
|
||||
|
||||
func fromNibWithoutConstraints() -> UIView? {
|
||||
guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? UIView else {
|
||||
return nil
|
||||
}
|
||||
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(contentView)
|
||||
|
||||
return contentView
|
||||
}
|
||||
|
||||
func fromNib() -> UIView? {
|
||||
guard let contentView = fromNibWithoutConstraints() else { return nil }
|
||||
|
||||
let bottomConstraint = NSLayoutConstraint(
|
||||
item: contentView,
|
||||
attribute: NSLayoutConstraint.Attribute.bottom,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: self,
|
||||
attribute: NSLayoutConstraint.Attribute.bottom,
|
||||
multiplier: 1,
|
||||
constant: 0
|
||||
)
|
||||
|
||||
let trailingConstraint = NSLayoutConstraint(
|
||||
item: contentView,
|
||||
attribute: NSLayoutConstraint.Attribute.trailing,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: self,
|
||||
attribute: NSLayoutConstraint.Attribute.trailing,
|
||||
multiplier: 1,
|
||||
constant: 0
|
||||
)
|
||||
|
||||
let topConstraint = NSLayoutConstraint(
|
||||
item: contentView,
|
||||
attribute: NSLayoutConstraint.Attribute.top,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: self,
|
||||
attribute: NSLayoutConstraint.Attribute.top,
|
||||
multiplier: 1,
|
||||
constant: 0
|
||||
)
|
||||
|
||||
let leadingConstraint = NSLayoutConstraint(
|
||||
item: contentView,
|
||||
attribute: NSLayoutConstraint.Attribute.leading,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: self,
|
||||
attribute: NSLayoutConstraint.Attribute.leading,
|
||||
multiplier: 1,
|
||||
constant: 0
|
||||
)
|
||||
|
||||
addConstraints([bottomConstraint, trailingConstraint, topConstraint, leadingConstraint])
|
||||
|
||||
return contentView
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Cell.swift
|
||||
// FigmaConvertXib
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 08.08.2020.
|
||||
// Copyright © 2020 mrusta. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UITableViewCell {
|
||||
|
||||
func separator(hide: Bool) {
|
||||
separatorInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: hide ? CGFloat(Double.greatestFiniteMagnitude) : 0.0)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
public func background(work: @escaping () -> Void) {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
work()
|
||||
}
|
||||
}
|
||||
|
||||
public func main(work: @escaping () -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
work()
|
||||
}
|
||||
}
|
||||
|
||||
public func main(delay: Double, work: @escaping () -> Void) {
|
||||
let deadline = DispatchTime.now() + delay
|
||||
DispatchQueue.main.asyncAfter(deadline: deadline) {
|
||||
work()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Font.swift
|
||||
// ContainerControllerSwift_Example
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 08.08.2024.
|
||||
// Copyright © 2024 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIFont {
|
||||
class func mediumSystemFont(ofSize pointSize: CGFloat) -> UIFont {
|
||||
return self.systemFont(ofSize: pointSize, weight: .medium)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Label.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 25.02.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UILabel {
|
||||
|
||||
func addUnderline(size: Int = 1) {
|
||||
|
||||
let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: self.text ?? "")
|
||||
attributeString.addAttribute(.underlineStyle, value: size, range: NSMakeRange(0, attributeString.length))
|
||||
|
||||
self.attributedText = attributeString
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func sizeToFitLabel(text: String?, font: UIFont? = nil, lines: Int = 99, padding: CGFloat = 0.0) -> CGSize {
|
||||
|
||||
let label = UILabel(frame: .init(x: 0, y: 0, width: 60, height: 15))
|
||||
label.font = font
|
||||
label.numberOfLines = lines
|
||||
label.text = text ?? ""
|
||||
label.sizeToFit()
|
||||
return .init(width: label.frame.size.width + (padding * 2), height: label.frame.size.height)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
//
|
||||
// String.swift
|
||||
// PlusBank
|
||||
//
|
||||
// Created by Valentin Titov on 02.02.2021.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol Stringable {
|
||||
var stringValue: String { get }
|
||||
}
|
||||
|
||||
protocol OptionalStringable {
|
||||
var stringValue: String? { get }
|
||||
}
|
||||
|
||||
//MARK: - Constants
|
||||
extension String {
|
||||
static let empty: String = ""
|
||||
static let whitespace: String = " "
|
||||
static let slash: String = "/"
|
||||
static let newLine: String = "\n"
|
||||
static let endLine: String = "\r\n"
|
||||
static let colon: String = ":"
|
||||
static let semicolon: String = ";"
|
||||
static let equal: String = "="
|
||||
static let and: String = "&"
|
||||
}
|
||||
|
||||
extension String {
|
||||
func substringInRange(_ range: NSRange) -> String {
|
||||
let range =
|
||||
index(startIndex, offsetBy: range.location)
|
||||
..<
|
||||
index(startIndex, offsetBy: range.location + range.length)
|
||||
return String(self[range])
|
||||
}
|
||||
|
||||
func replaceStringInRange(
|
||||
_ range: NSRange,
|
||||
with string: String
|
||||
) -> String {
|
||||
return (self as NSString)
|
||||
.replacingCharacters(in: range, with: string)
|
||||
.trimmingCharacters(in: .whitespaces) as String
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func index(from: Int) -> Index {
|
||||
return self.index(startIndex, offsetBy: from)
|
||||
}
|
||||
|
||||
func substring(from: Int) -> String {
|
||||
let fromIndex = index(from: from)
|
||||
return String(self[fromIndex...])
|
||||
}
|
||||
|
||||
func substring(to: Int) -> String {
|
||||
let toIndex = index(from: to)
|
||||
return String(self[..<toIndex])
|
||||
}
|
||||
|
||||
func substring(with r: Range<Int>) -> String {
|
||||
let startIndex = index(from: r.lowerBound)
|
||||
let endIndex = index(from: r.upperBound)
|
||||
return String(self[startIndex..<endIndex])
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
subscript (bounds: CountableClosedRange<Int>) -> String {
|
||||
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||
return String(self[start...end])
|
||||
}
|
||||
|
||||
subscript (bounds: CountableRange<Int>) -> String {
|
||||
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||
return String(self[start..<end])
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Letters formatting
|
||||
extension String {
|
||||
var firstCapitalized: String {
|
||||
prefix(1).capitalized + dropFirst()
|
||||
}
|
||||
|
||||
var firstLowercased: String {
|
||||
prefix(1).lowercased() + dropFirst()
|
||||
}
|
||||
|
||||
var positiveStringNumber: String {
|
||||
return "+ \(self)"
|
||||
}
|
||||
|
||||
var negativeStringNumber: String {
|
||||
return "- \(self)"
|
||||
}
|
||||
|
||||
var titleCase: String {
|
||||
return self
|
||||
.replacingOccurrences(of: "([A-Z])",
|
||||
with: " $1",
|
||||
options: .regularExpression,
|
||||
range: range(of: self))
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.capitalized
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
var normalizePhone: String {
|
||||
let decimalPhone = self.decimalString() as String
|
||||
let formatted: String
|
||||
if let symb = decimalPhone.first, (symb == "7" || symb == "8") {
|
||||
formatted = String(decimalPhone.dropFirst())
|
||||
} else {
|
||||
formatted = decimalPhone
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
func decimalString() -> NSString {
|
||||
return components(separatedBy: NSCharacterSet.decimalDigits.inverted).joined() as NSString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension String {
|
||||
|
||||
func addHighlight(texts: [String],
|
||||
color: UIColor = .systemBlue) -> NSMutableAttributedString {
|
||||
|
||||
let attr = NSMutableAttributedString(string: self, attributes: nil)
|
||||
|
||||
for text in texts {
|
||||
let range = (attr.string as NSString).range(of: text)
|
||||
attr.setAttributes([.foregroundColor: color], range: range)
|
||||
}
|
||||
|
||||
return attr
|
||||
}
|
||||
|
||||
func addUnderline() -> NSMutableAttributedString {
|
||||
|
||||
let textRange = NSRange(location: 0, length: self.count)
|
||||
let attr = NSMutableAttributedString(string: self)
|
||||
attr.addAttribute(.underlineStyle,
|
||||
value: NSUnderlineStyle.single.rawValue,
|
||||
range: textRange)
|
||||
|
||||
return attr
|
||||
}
|
||||
|
||||
// func remove(currency: CurrencyType) -> String {
|
||||
// let value = self
|
||||
// .replacingOccurrences(of: currency.symbol, with: "")
|
||||
// .replacingOccurrences(of: " ", with: "")
|
||||
// return value
|
||||
// }
|
||||
//
|
||||
// func formattingRemoveSpaces() -> String {
|
||||
// return removeSpaces().findReplace(find: " ", replace: "", all: true)
|
||||
// }
|
||||
//
|
||||
// func currencyInputFormatting() -> String {
|
||||
//
|
||||
// func formatting(text: String, digits: Int) -> String {
|
||||
// let formatting =
|
||||
// text.currencyInputFullFormatting(digits: digits, spaces: false)
|
||||
// .remove(currency: .rub)
|
||||
// .removeSpaces()
|
||||
// return formatting
|
||||
// }
|
||||
//
|
||||
// let removeSpace = self.formattingRemoveSpaces()
|
||||
//
|
||||
// let needle: Character = ","
|
||||
// if let idx = removeSpace.firstIndex(of: needle) {
|
||||
// let pos = removeSpace.distance(from: removeSpace.startIndex, to: idx)
|
||||
//
|
||||
//
|
||||
// let result1 = removeSpace.components(separatedBy: ",")
|
||||
//
|
||||
// if result1.count > 2 {
|
||||
//
|
||||
// let result = String(removeSpace.dropLast())
|
||||
// return result
|
||||
//
|
||||
// } else if result1.count > 1 {
|
||||
//
|
||||
// let components = result1[1]
|
||||
// print("components \(components.count) ")
|
||||
//
|
||||
// if components.count != 0 {
|
||||
//
|
||||
// var digits = 0
|
||||
// let digitsMax = 2
|
||||
// if components.count > digitsMax {
|
||||
// let newComponents = String(components.prefix(2))
|
||||
// let text = result1[0] + newComponents
|
||||
//
|
||||
// return formatting(text: text, digits: digitsMax)
|
||||
// }
|
||||
// else if digitsMax >= components.count {
|
||||
// digits = components.count
|
||||
// }
|
||||
//
|
||||
// let text = result1[0] + components
|
||||
// return formatting(text: text, digits: digits)
|
||||
// } else {
|
||||
// return removeSpace // formatting(text: removeSpace, digits: 0)
|
||||
// }
|
||||
//
|
||||
//
|
||||
// }
|
||||
//
|
||||
// print("Found \(needle) at position \(pos) -- \(result1.count) ")
|
||||
//
|
||||
// return removeSpace // formatting(text: removeSpace, digits: 0)
|
||||
// }
|
||||
// else {
|
||||
// print("Not found")
|
||||
// return removeSpace
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// func currencyInputFullFormatting(digits: Int, spaces: Bool = true) -> String {
|
||||
//
|
||||
// var number: NSNumber!
|
||||
// let formatter = NumberFormatter()
|
||||
// formatter.numberStyle = .currencyAccounting
|
||||
// formatter.currencySymbol = "₽"
|
||||
// formatter.maximumFractionDigits = digits
|
||||
// formatter.minimumFractionDigits = digits
|
||||
//
|
||||
// var amountWithPrefix = self
|
||||
//
|
||||
// // remove from String: "$", ".", ","
|
||||
// let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
|
||||
// amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.count), withTemplate: "")
|
||||
//
|
||||
// var value: Double = 100
|
||||
// if digits == 1 {
|
||||
// value = 10
|
||||
// } else if digits == 0 {
|
||||
// value = 1
|
||||
// }
|
||||
//
|
||||
// let double = (amountWithPrefix as NSString).doubleValue
|
||||
// number = NSNumber(value: (double / value))
|
||||
//
|
||||
// // if first number is 0 or all numbers were deleted
|
||||
// guard number != 0 as NSNumber else {
|
||||
// return ""
|
||||
// }
|
||||
//
|
||||
// let result = formatter.string(from: number)!
|
||||
// if !spaces {
|
||||
// return result.formattingRemoveSpaces()
|
||||
// }
|
||||
// return result
|
||||
// }
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
func find(_ find: String) -> Bool {
|
||||
range(of: find) != nil
|
||||
}
|
||||
|
||||
func indexInt(of char: Character, last: Bool = false) -> (index: Index, number: Int)? {
|
||||
if last {
|
||||
guard let index = lastIndex(of: char) else { return nil }
|
||||
return ( index, index.utf16Offset(in: self) )
|
||||
} else {
|
||||
guard let index = firstIndex(of: char) else { return nil }
|
||||
return ( index, index.utf16Offset(in: self) )
|
||||
}
|
||||
}
|
||||
|
||||
func findReplace(find: String, replace: String, all: Bool = false) -> String {
|
||||
if all {
|
||||
return replacingOccurrences(of: find, with: replace, options: .literal, range: nil)
|
||||
} else {
|
||||
return replacingOccurrences(of: find, with: replace)
|
||||
}
|
||||
}
|
||||
|
||||
func removeSpaces() -> String {
|
||||
trimmingCharacters(in: .whitespaces)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension String {
|
||||
func removingEmoji() -> String {
|
||||
return String(self.filter { !$0.isEmoji() })
|
||||
}
|
||||
func removeEmoji() -> String {
|
||||
return self.components(separatedBy: CharacterSet.symbols).joined()
|
||||
}
|
||||
}
|
||||
|
||||
extension Character {
|
||||
fileprivate func isEmoji() -> Bool {
|
||||
return
|
||||
Character(UnicodeScalar(UInt32(0x1d000))!) <= self && self <= Character(UnicodeScalar(UInt32(0x1f77f))!) ||
|
||||
Character(UnicodeScalar(UInt32(0x2100))!) <= self && self <= Character(UnicodeScalar(UInt32(0x26ff))!) ||
|
||||
|
||||
Character(UnicodeScalar(UInt32(0x1f600))!) <= self && self <= Character(UnicodeScalar(UInt32(0x1f64f))!) ||
|
||||
Character(UnicodeScalar(UInt32(0x1f300))!) <= self && self <= Character(UnicodeScalar(UInt32(0x1f5ff))!) ||
|
||||
Character(UnicodeScalar(UInt32(0x1f680))!) <= self && self <= Character(UnicodeScalar(UInt32(0x1f5ff))!) ||
|
||||
Character(UnicodeScalar(UInt32(0x2600))!) <= self && self <= Character(UnicodeScalar(UInt32(0x2600))!) ||
|
||||
Character(UnicodeScalar(UInt32(0x2700))!) <= self && self <= Character(UnicodeScalar(UInt32(0x27bf))!) ||
|
||||
Character(UnicodeScalar(UInt32(0xfe00))!) <= self && self <= Character(UnicodeScalar(UInt32(0xfe0f))!)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Name Of Class
|
||||
|
||||
extension NSObject {
|
||||
class var nameClss: String{
|
||||
return NSStringFromClass(self).components(separatedBy: ".").last!
|
||||
}
|
||||
var nameClss: String{
|
||||
return NSStringFromClass(type(of: self)).components(separatedBy: ".").last!
|
||||
}
|
||||
var nameObj: String{
|
||||
return String(describing: type(of: Self.self))
|
||||
}
|
||||
func fontSize(se: Bool = false) -> UIFont {
|
||||
return UIFont.systemFont(ofSize: se && Device.SE ? 16 : 18)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// UIView+AutoLayout.swift
|
||||
// ContainerControllerSwift_Example
|
||||
//
|
||||
// Created by Рустам Мотыгуллин on 12.08.2024.
|
||||
// Copyright © 2024 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
//MARK: - Round
|
||||
extension UIView {
|
||||
|
||||
|
||||
|
||||
func addConstraintsZerro(view: UIView?, superView: UIView?, padding: CGFloat) {
|
||||
|
||||
|
||||
// let padding: CGFloat = 0
|
||||
|
||||
if let mainCardView = superView, let view3 = view {
|
||||
|
||||
|
||||
view3.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
|
||||
let bottomConstraint = NSLayoutConstraint(
|
||||
item: mainCardView,
|
||||
attribute: NSLayoutConstraint.Attribute.bottom,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: view3,
|
||||
attribute: NSLayoutConstraint.Attribute.bottom,
|
||||
multiplier: 1,
|
||||
constant: (padding * -1)
|
||||
)
|
||||
|
||||
let trailingConstraint = NSLayoutConstraint(
|
||||
item: mainCardView,
|
||||
attribute: NSLayoutConstraint.Attribute.trailing,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: view3,
|
||||
attribute: NSLayoutConstraint.Attribute.trailing,
|
||||
multiplier: 1,
|
||||
constant: (padding * -1)
|
||||
)
|
||||
|
||||
let topConstraint = NSLayoutConstraint(
|
||||
item: mainCardView,
|
||||
attribute: NSLayoutConstraint.Attribute.top,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: view3,
|
||||
attribute: NSLayoutConstraint.Attribute.top,
|
||||
multiplier: 1,
|
||||
constant: padding
|
||||
)
|
||||
|
||||
let leadingConstraint = NSLayoutConstraint(
|
||||
item: mainCardView,
|
||||
attribute: NSLayoutConstraint.Attribute.leading,
|
||||
relatedBy: NSLayoutConstraint.Relation.equal,
|
||||
toItem: view3,
|
||||
attribute: NSLayoutConstraint.Attribute.leading,
|
||||
multiplier: 1,
|
||||
constant: padding
|
||||
)
|
||||
|
||||
addConstraints([bottomConstraint, trailingConstraint, topConstraint, leadingConstraint])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
//
|
||||
// UIView+Positioning.swift
|
||||
//
|
||||
// Created by Shai Mishali on 1/19/15.
|
||||
// Copyright (c) 2015 Shai Mishali. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
//MARK: - Round
|
||||
extension UIView {
|
||||
|
||||
func set(x:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.origin.x = x
|
||||
self.frame = frame
|
||||
}
|
||||
|
||||
func set(y:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.origin.y = y
|
||||
self.frame = frame
|
||||
}
|
||||
|
||||
func set(width:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.size.width = width
|
||||
self.frame = frame
|
||||
}
|
||||
|
||||
func set(height:CGFloat) {
|
||||
var frame:CGRect = self.frame
|
||||
frame.size.height = height
|
||||
self.frame = frame
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIView {
|
||||
|
||||
// MARK: - Basic Properties
|
||||
|
||||
/// X Axis value of UIView.
|
||||
var x: CGFloat {
|
||||
set { self.frame = CGRect(x: _pixelIntegral(newValue),
|
||||
y: self.y,
|
||||
width: self.width,
|
||||
height: self.height)
|
||||
}
|
||||
get { return self.frame.origin.x }
|
||||
}
|
||||
|
||||
/// Y Axis value of UIView.
|
||||
var y: CGFloat {
|
||||
set { self.frame = CGRect(x: self.x,
|
||||
y: _pixelIntegral(newValue),
|
||||
width: self.width,
|
||||
height: self.height)
|
||||
}
|
||||
get { return self.frame.origin.y }
|
||||
}
|
||||
|
||||
/// Width of view.
|
||||
var width: CGFloat {
|
||||
set { self.frame = CGRect(x: self.x,
|
||||
y: self.y,
|
||||
width: _pixelIntegral(newValue),
|
||||
height: self.height)
|
||||
}
|
||||
get { return self.frame.size.width }
|
||||
}
|
||||
|
||||
/// Height of view.
|
||||
var height: CGFloat {
|
||||
set { self.frame = CGRect(x: self.x,
|
||||
y: self.y,
|
||||
width: self.width,
|
||||
height: _pixelIntegral(newValue))
|
||||
}
|
||||
get { return self.frame.size.height }
|
||||
}
|
||||
|
||||
// MARK: - Origin and Size
|
||||
|
||||
/// View's Origin point.
|
||||
var origin: CGPoint {
|
||||
set { self.frame = CGRect(x: _pixelIntegral(newValue.x),
|
||||
y: _pixelIntegral(newValue.y),
|
||||
width: self.width,
|
||||
height: self.height)
|
||||
}
|
||||
get { return self.frame.origin }
|
||||
}
|
||||
|
||||
/// View's size.
|
||||
var size: CGSize {
|
||||
set { self.frame = CGRect(x: self.x,
|
||||
y: self.y,
|
||||
width: _pixelIntegral(newValue.width),
|
||||
height: _pixelIntegral(newValue.height))
|
||||
}
|
||||
get { return self.frame.size }
|
||||
}
|
||||
|
||||
// MARK: - Extra Properties
|
||||
|
||||
/// View's right side (x + width).
|
||||
var right: CGFloat {
|
||||
set { self.x = newValue - self.width }
|
||||
get { return self.x + self.width }
|
||||
}
|
||||
|
||||
/// View's bottom (y + height).
|
||||
var bottom: CGFloat {
|
||||
set { self.y = newValue - self.height }
|
||||
get { return self.y + self.height }
|
||||
}
|
||||
|
||||
/// View's top (y).
|
||||
var top: CGFloat {
|
||||
set { self.y = newValue }
|
||||
get { return self.y }
|
||||
}
|
||||
|
||||
/// View's left side (x).
|
||||
var left: CGFloat {
|
||||
set { self.x = newValue }
|
||||
get { return self.x }
|
||||
}
|
||||
|
||||
/// View's center X value (center.x).
|
||||
var centerX: CGFloat {
|
||||
set { self.center = CGPoint(x: newValue, y: self.centerY) }
|
||||
get { return self.center.x }
|
||||
}
|
||||
|
||||
/// View's center Y value (center.y).
|
||||
var centerY: CGFloat {
|
||||
set { self.center = CGPoint(x: self.centerX, y: newValue) }
|
||||
get { return self.center.y }
|
||||
}
|
||||
|
||||
/// Last subview on X Axis.
|
||||
var lastSubviewOnX: UIView? {
|
||||
return self.subviews.reduce(UIView(frame: .zero)) {
|
||||
return $1.x > $0.x ? $1 : $0
|
||||
}
|
||||
}
|
||||
|
||||
/// Last subview on Y Axis.
|
||||
var lastSubviewOnY: UIView? {
|
||||
return self.subviews.reduce(UIView(frame: .zero)) {
|
||||
return $1.y > $0.y ? $1 : $0
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Bounds Methods
|
||||
|
||||
/// X value of bounds (bounds.origin.x).
|
||||
var boundsX: CGFloat {
|
||||
set { self.bounds = CGRect(x: _pixelIntegral(newValue),
|
||||
y: self.boundsY,
|
||||
width: self.boundsWidth,
|
||||
height: self.boundsHeight)
|
||||
}
|
||||
get { return self.bounds.origin.x }
|
||||
}
|
||||
|
||||
/// Y value of bounds (bounds.origin.y).
|
||||
var boundsY: CGFloat {
|
||||
set { self.frame = CGRect(x: self.boundsX,
|
||||
y: _pixelIntegral(newValue),
|
||||
width: self.boundsWidth,
|
||||
height: self.boundsHeight)
|
||||
}
|
||||
get { return self.bounds.origin.y }
|
||||
}
|
||||
|
||||
/// Width of bounds (bounds.size.width).
|
||||
var boundsWidth: CGFloat {
|
||||
set { self.frame = CGRect(x: self.boundsX,
|
||||
y: self.boundsY,
|
||||
width: _pixelIntegral(newValue),
|
||||
height: self.boundsHeight)
|
||||
}
|
||||
get { return self.bounds.size.width }
|
||||
}
|
||||
|
||||
/// Height of bounds (bounds.size.height).
|
||||
var boundsHeight: CGFloat {
|
||||
set { self.frame = CGRect(x: self.boundsX,
|
||||
y: self.boundsY,
|
||||
width: self.boundsWidth,
|
||||
height: _pixelIntegral(newValue))
|
||||
}
|
||||
get { return self.bounds.size.height }
|
||||
}
|
||||
|
||||
// MARK: - Useful Methods
|
||||
|
||||
/// Center view to it's parent view.
|
||||
// func centerToParent() {
|
||||
// guard let superview = self.superview else { return }
|
||||
//
|
||||
// switch UIApplication.shared.statusBarOrientation {
|
||||
// case .landscapeLeft, .landscapeRight:
|
||||
// self.origin = CGPoint(x: (superview.height / 2) - (self.width / 2),
|
||||
// y: (superview.width / 2) - (self.height / 2))
|
||||
// case .portrait, .portraitUpsideDown:
|
||||
// self.origin = CGPoint(x: (superview.width / 2) - (self.width / 2),
|
||||
// y: (superview.height / 2) - (self.height / 2))
|
||||
// case .unknown:
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
// MARK: - Private Methods
|
||||
fileprivate func _pixelIntegral(_ pointValue: CGFloat) -> CGFloat {
|
||||
let scale = UIScreen.main.scale
|
||||
return (round(pointValue * scale) / scale)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom": "iphone",
|
||||
"filename" : "appIcon-20@2x.png",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom": "iphone",
|
||||
"filename" : "appIcon-20@3x.png",
|
||||
"scale": "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom": "ipad",
|
||||
"filename" : "appIcon-20.png",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom": "ipad",
|
||||
"filename" : "appIcon-20@2x.png",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "appIcon-29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "appIcon-29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "appIcon-40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "appIcon-40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "appIcon-60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "appIcon-60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "appIcon-29.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "appIcon-29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "appIcon-40.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "appIcon-40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "appIcon-76.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "appIcon-76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "appIcon-83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "appIcon-1024.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 404 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user