Version 1.0.0

This commit is contained in:
Daniel Arantes Loverde
2025-03-23 00:27:49 -03:00
commit e4538f84e2
74 changed files with 12161 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

24
LICENSE.md Executable file
View File

@@ -0,0 +1,24 @@
![](loverde_company_logo_full.png)
Copyright (C) Loverde Company - All Rights Reserved
----------
Licensed under the MIT license
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.

16
Package.swift Normal file
View File

@@ -0,0 +1,16 @@
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "LCEssentials",
products: [
.library(
name: "LCEssentials",
targets: ["LCEssentials"]),
],
targets: [
.target(
name: "LCEssentials"),
]
)

70
README.md Normal file
View File

@@ -0,0 +1,70 @@
![](loverde_company_logo_full.png)
Loverde Co. Essentials Swift Scripts
----
This is a repository of essential scripts written in Swift for Loverde Co. used to save time on re-writing and keeping it on all other projects. So this Cocoapods will evolve with Swift and will improve with every release!
## Requirements
- iOS 15.* or newer, Swift 5.* or newer.
## Features
- [x] Many usefull scripts extensions
Installation
----
#### Swift Package Manager (SPM)
``` swift
dependencies: [
.package(url: "https://github.com/loverde-co/LCEssentials.git", .upToNextMajor(from: "1.0.0"))
]
```
You can also add it via XCode SPM editor with URL:
``` swift
https://github.com/loverde-co/LCEssentials.git
```
## Usage example
* Background Trhead
```swift
LCEssentials.backgroundThread(delay: 0.6, background: {
//Do something im background
}) {
//When finish, update UI
}
```
* NavigationController with Completion Handler
```swift
self.navigationController?.popViewControllerWithHandler(completion: {
//Do some stuff after pop
})
//or more simple
self.navigationController?.popViewControllerWithHandler {
//Do some stuff after pop
}
```
## Another components
> LCESnackBarView - **great way to send feedback to user**
And then import `LCEssentials ` wherever you import UIKit or SwiftUI
``` swift
import LCEssentials
```
Author:
----
Any question or doubts, please send thru email
Daniel Arantes Loverde - <daniel@loverde.com.br>
[![Alt text](https://loverde.com.br/_signature/loverde_github_mail.gif "My Resume")](https://github.com/loverde-co/resume/)
[![Alt text](https://loverde.com.br/_signature/loverde_github_mail.gif "Loverde Co. Github")](https://github.com/loverde-co)

View File

@@ -0,0 +1,247 @@
//
// iOSDevCenters+GIF.swift
// GIF-Swift
//
// Created by iOSDevCenters on 11/12/15.
// Copyright © 2016 iOSDevCenters. All rights reserved.
//
import UIKit
#if os(iOS) || os(macOS)
import ImageIO
//let jeremyGif = UIImage.gifWithName("jeremy")
//let imageView = UIImageView(...)
// Uncomment the next line to prevent stretching the image
// imageView.contentMode = .ScaleAspectFit
// Uncomment the next line to set a gray color.
// You can also set a default image which get's displayed
// after the animation
// imageView.backgroundColor = UIColor.grayColor()
// Set the images from the UIImage
//imageView.animationImages = jeremyGif?.images
// Set the duration of the UIImage
//imageView.animationDuration = jeremyGif!.duration
// Set the repetitioncount
//imageView.animationRepeatCount = 1
// Start the animation
//imageView.startAnimating()
extension UIImageView {
public func loadGif(name: String) {
DispatchQueue.global().async {
let image = UIImage.gif(name: name)
DispatchQueue.main.async {
self.image = image
}
}
}
@available(iOS 9.0, *)
public func loadGif(asset: String) {
DispatchQueue.global().async {
let image = UIImage.gif(asset: asset)
DispatchQueue.main.async {
self.image = image
}
}
}
}
extension UIImage {
public class func gif(data: Data) -> UIImage? {
// Create source from data
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
print("SwiftGif: Source for the image does not exist")
return nil
}
return UIImage.animatedImageWithSource(source)
}
public class func gif(url: String) -> UIImage? {
// Validate URL
guard let bundleURL = URL(string: url) else {
print("SwiftGif: This image named \"\(url)\" does not exist")
return nil
}
// Validate data
guard let imageData = try? Data(contentsOf: bundleURL) else {
print("SwiftGif: Cannot turn image named \"\(url)\" into NSData")
return nil
}
return gif(data: imageData)
}
public class func gif(name: String) -> UIImage? {
// Check for existance of gif
guard let bundleURL = Bundle.main
.url(forResource: name, withExtension: "gif") else {
print("SwiftGif: This image named \"\(name)\" does not exist")
return nil
}
// Validate data
guard let imageData = try? Data(contentsOf: bundleURL) else {
print("SwiftGif: Cannot turn image named \"\(name)\" into NSData")
return nil
}
return gif(data: imageData)
}
@available(iOS 9.0, *)
public class func gif(asset: String) -> UIImage? {
// Create source from assets catalog
guard let dataAsset = NSDataAsset(name: asset) else {
print("SwiftGif: Cannot turn image named \"\(asset)\" into NSDataAsset")
return nil
}
return gif(data: dataAsset.data)
}
internal class func delayForImageAtIndex(_ index: Int, source: CGImageSource!) -> Double {
var delay = 0.1
// Get dictionaries
let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil)
let gifPropertiesPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 0)
defer {
gifPropertiesPointer.deallocate()
}
let unsafePointer = Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()
if CFDictionaryGetValueIfPresent(cfProperties, unsafePointer, gifPropertiesPointer) == false {
return delay
}
let gifProperties: CFDictionary = unsafeBitCast(gifPropertiesPointer.pointee, to: CFDictionary.self)
// Get delay time
var delayObject: AnyObject = unsafeBitCast(
CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()),
to: AnyObject.self)
if delayObject.doubleValue == 0 {
delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self)
}
if let delayObject = delayObject as? Double, delayObject > 0 {
delay = delayObject
} else {
delay = 0.1 // Make sure they're not too fast
}
return delay
}
internal class func gcdForPair(_ lhs: Int?, _ rhs: Int?) -> Int {
var lhs = lhs
var rhs = rhs
// Check if one of them is nil
if rhs == nil || lhs == nil {
if rhs != nil {
return rhs!
} else if lhs != nil {
return lhs!
} else {
return 0
}
}
// Swap for modulo
if lhs! < rhs! {
let ctp = lhs
lhs = rhs
rhs = ctp
}
// Get greatest common divisor
var rest: Int
while true {
rest = lhs! % rhs!
if rest == 0 {
return rhs! // Found it
} else {
lhs = rhs
rhs = rest
}
}
}
internal class func gcdForArray(_ array: [Int]) -> Int {
if array.isEmpty {
return 1
}
var gcd = array[0]
for val in array {
gcd = UIImage.gcdForPair(val, gcd)
}
return gcd
}
internal class func animatedImageWithSource(_ source: CGImageSource) -> UIImage? {
let count = CGImageSourceGetCount(source)
var images = [CGImage]()
var delays = [Int]()
// Fill arrays
for index in 0..<count {
// Add image
if let image = CGImageSourceCreateImageAtIndex(source, index, nil) {
images.append(image)
}
// At it's delay in cs
let delaySeconds = UIImage.delayForImageAtIndex(Int(index),
source: source)
delays.append(Int(delaySeconds * 1000.0)) // Seconds to ms
}
// Calculate full duration
let duration: Int = {
var sum = 0
for val: Int in delays {
sum += val
}
return sum
}()
// Get frames
let gcd = gcdForArray(delays)
var frames = [UIImage]()
var frame: UIImage
var frameCount: Int
for index in 0..<count {
frame = UIImage(cgImage: images[Int(index)])
frameCount = Int(delays[Int(index)] / gcd)
for _ in 0..<frameCount {
frames.append(frame)
}
}
// Heyhey
let animation = UIImage.animatedImage(with: frames,
duration: Double(duration) / 1000.0)
return animation
}
}
#endif

View File

@@ -0,0 +1,419 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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
#if os(iOS) || os(watchOS)
#if canImport(Security)
import Security
#endif
#if canImport(UIKit)
import UIKit
#endif
public enum Result<Value, Error: Swift.Error> {
case success(Value)
case failure(Error)
}
public enum httpMethod: String {
case post = "POST"
case get = "GET"
case put = "PUT"
case delete = "DELETE"
}
/// Loverde Co.: API generic struct for simple requests
@available(iOS 13.0.0, *)
@MainActor
public struct API {
private static var certData: Data?
private static var certPassword: String?
static let defaultError = NSError.createErrorWith(code: LCEssentials.DEFAULT_ERROR_CODE,
description: LCEssentials.DEFAULT_ERROR_MSG,
reasonForError: LCEssentials.DEFAULT_ERROR_MSG)
public static var persistConnectionDelay: Double = 3
public static var defaultParams: [String:Any] = [String: Any]()
var defaultHeaders: [String: String] = ["Accept": "application/json",
"Content-Type": "application/json; charset=UTF-8",
"Accept-Encoding": "gzip"]
public static let shared = API()
private init(){}
public func request<T: Codable>(url: String,
params: Any? = nil,
method: httpMethod,
headers: [String: String] = [:],
jsonEncoding: Bool = true,
debug: Bool = true,
timeoutInterval: TimeInterval = 30,
networkServiceType: URLRequest.NetworkServiceType = .default,
persistConnection: Bool = false) async throws -> T {
if let urlReq = URL(string: url.replaceURL(params as? [String: Any] ?? [:] )) {
var request = URLRequest(url: urlReq, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
if method == .post || method == .put || method == .delete {
if let params = params as? [String: Any],
let pathFile = params["file"] as? String,
let fileURL = URL(string: pathFile) {
let boundary = UUID().uuidString
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var body = Data()
// Adiciona campos adicionais (se houver)
for (key, value) in params where key != "file" {
let stringValue = "\(value)"
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
body.append("\(stringValue)\r\n".data(using: .utf8)!)
}
// Adiciona o arquivo
let fileName = fileURL.lastPathComponent
let mimeType = mimeTypeForPath(path: fileName)
do {
let fileData = try Data(contentsOf: fileURL)
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
body.append("Content-Type: \(mimeType)\r\n\r\n".data(using: .utf8)!)
body.append(fileData)
body.append("\r\n".data(using: .utf8)!)
} catch {
printError(title: "Upload File", msg: error.localizedDescription)
}
// Finaliza o corpo da requisição
body.append("--\(boundary)--\r\n".data(using: .utf8)!)
request.httpBody = body
request.setValue("\(body.count)", forHTTPHeaderField: "Content-Length")
// Logs de depuração
printLog(title: "Boundary", msg: boundary)
if let bodyString = String(data: body, encoding: .utf8) {
printLog(title: "Body Content", msg: bodyString)
}
} else if jsonEncoding, let params = params as? [String: Any] {
let requestObject = try JSONSerialization.data(withJSONObject: params)
request.httpBody = requestObject
} else if let params = params as? [String: Any] {
var bodyComponents = URLComponents()
params.forEach({ (key, value) in
bodyComponents.queryItems?.append(URLQueryItem(name: key, value: value as? String))
})
request.httpBody = bodyComponents.query?.data(using: .utf8)
} else if let params = params as? Data {
request.httpBody = params
}
}
request.httpMethod = method.rawValue
request.timeoutInterval = timeoutInterval
request.networkServiceType = networkServiceType
// - Put Default Headers togheter with user defined params
if !headers.isEmpty {
// - Add it to request
headers.forEach { (key, value) in
request.addValue(value, forHTTPHeaderField: key)
}
}else{
defaultHeaders.forEach { (key, value) in
request.addValue(value, forHTTPHeaderField: key)
}
}
if debug {
API.requestLOG(method: method, request: request)
}
let session = URLSession(
configuration: .default,
delegate: URLSessionDelegateHandler(
certData: API.certData,
password: API.certPassword
),
delegateQueue: nil
)
do {
let (data, response) = try await session.data(for: request)
var code: Int = LCEssentials.DEFAULT_ERROR_CODE
let httpResponse = response as? HTTPURLResponse ?? HTTPURLResponse()
code = httpResponse.statusCode
let error = URLError(URLError.Code(rawValue: code))
switch code {
case 200..<300:
// - Debug LOG
if debug {
API.responseLOG(method: method, request: request, data: data, statusCode: code, error: nil)
}
// - Check if is JSON result and try decode it
if let string = data.string as? T, T.self == String.self {
return string
}
// - Normal decoding
do {
return try JSONDecoder.decode(data: data)
} catch {
printError(title: "JSONDecoder", msg: error.localizedDescription)
throw error
}
case 400..<500:
// - Debug LOG
if debug {
API.responseLOG(method: method, request: request, data: data, statusCode: code, error: error)
}
if persistConnection {
printError(title: "INTERNET CONNECTION ERROR", msg: "WILL PERSIST")
let persist: T = try await self.request(
url: url,
params: params,
method: method,
headers: headers,
jsonEncoding: jsonEncoding,
debug: debug,
timeoutInterval: timeoutInterval,
networkServiceType: networkServiceType,
persistConnection: persistConnection
)
return persist
} else {
if debug {
API.responseLOG(method: method, request: request, data: data, statusCode: code, error: error)
}
let friendlyError = NSError.createErrorWith(code: code, description: error.localizedDescription, reasonForError: data.prettyJson ?? "")
throw friendlyError
}
default:
// - Debug LOG
if debug {
API.responseLOG(method: method, request: request, data: data, statusCode: code, error: error)
}
let friendlyError = NSError.createErrorWith(code: code, description: error.localizedDescription, reasonForError: data.prettyJson ?? "")
throw friendlyError
}
} catch {
throw error
}
}
throw API.defaultError
}
public func setupCertificationRequest(certData: Data, password: String = "") {
API.certData = certData
API.certPassword = password
}
}
#if canImport(Security)
@available(iOS 13.0.0, *)
@MainActor
private class URLSessionDelegateHandler: NSObject, URLSessionDelegate {
private var certData: Data?
private var certPass: String?
init(certData: Data? = nil, password: String? = nil) {
super.init()
self.certData = certData
self.certPass = password
}
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
// Carregar o certificado do cliente
guard let identity = getIdentity() else {
return (URLSession.AuthChallengeDisposition.performDefaultHandling, nil)
}
// Criar o URLCredential com a identidade
let credential = URLCredential(identity: identity, certificates: nil, persistence: .forSession)
return (URLSession.AuthChallengeDisposition.useCredential, credential)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
// Validar o certificado do servidor
if let serverTrust = challenge.protectionSpace.serverTrust {
let serverCredential = URLCredential(trust: serverTrust)
return (URLSession.AuthChallengeDisposition.useCredential, serverCredential)
} else {
return (URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
}
} else {
return (URLSession.AuthChallengeDisposition.performDefaultHandling, nil)
}
}
private func getIdentity() -> SecIdentity? {
guard let certData = self.certData else { return nil }
// Especifique a senha usada ao exportar o .p12
let options: [String: Any] = [kSecImportExportPassphrase as String: self.certPass ?? ""]
var items: CFArray?
// Importar o certificado .p12 para obter a identidade
let status = SecPKCS12Import(certData as CFData, options as CFDictionary, &items)
if status == errSecSuccess,
let item = (items as? [[String: Any]])?.first,
let identityRef = item[kSecImportItemIdentity as String] as CFTypeRef?,
CFGetTypeID(identityRef) == SecIdentityGetTypeID() {
return (identityRef as! SecIdentity)
} else {
print("Erro ao importar a identidade do certificado: \(status)")
return nil
}
}
private func isPKCS12(data: Data, password: String) -> Bool {
let options: [String: Any] = [kSecImportExportPassphrase as String: password]
var items: CFArray?
let status = SecPKCS12Import(data as CFData, options as CFDictionary, &items)
return status == errSecSuccess
}
}
#endif
extension Error {
public var statusCode: Int {
get{
return self._code
}
}
}
@available(iOS 13.0.0, *)
extension API {
fileprivate static func requestLOG(method: httpMethod, request: URLRequest) {
print("\n<========================= 🟠 INTERNET CONNECTION - REQUEST =========================>")
printLog(title: "DATE AND TIME", msg: Date().debugDescription)
printLog(title: "METHOD", msg: method.rawValue)
printLog(title: "REQUEST", msg: String(describing: request))
printLog(title: "HEADERS", msg: request.allHTTPHeaderFields?.debugDescription ?? "")
//
if let dataBody = request.httpBody, let prettyJson = dataBody.prettyJson {
printLog(title: "PARAMETERS", msg: prettyJson)
} else if let dataBody = request.httpBody {
printLog(title: "PARAMETERS", msg: String(data: dataBody, encoding: .utf8) ?? "-")
}
//
print("<======================================================================================>")
}
fileprivate static func responseLOG(method: httpMethod, request: URLRequest, data: Data?, statusCode: Int, error: Error?) {
///
let icon = error != nil ? "🔴" : "🟢"
print("\n<========================= \(icon) INTERNET CONNECTION - RESPONSE =========================>")
printLog(title: "DATE AND TIME", msg: Date().debugDescription)
printLog(title: "METHOD", msg: method.rawValue)
printLog(title: "REQUEST", msg: String(describing: request))
printLog(title: "HEADERS", msg: request.allHTTPHeaderFields?.debugDescription ?? "")
//
if let dataBody = request.httpBody, let prettyJson = dataBody.prettyJson {
printLog(title: "PARAMETERS", msg: prettyJson)
} else if let dataBody = request.httpBody {
printLog(title: "PARAMETERS", msg: String(data: dataBody, encoding: .utf8) ?? "-")
}
//
printLog(title: "STATUS CODE", msg: String(describing: statusCode))
//
if let dataResponse = data, let prettyJson = dataResponse.prettyJson {
printLog(title: "RESPONSE", msg: prettyJson)
} else {
printLog(title: "RESPONSE", msg: String(data: data ?? Data(), encoding: .utf8) ?? "-")
}
//
if let error = error {
switch error.statusCode {
case NSURLErrorTimedOut:
printError(title: "RESPONSE ERROR TIMEOUT", msg: "DESCRICAO: \(error.localizedDescription)")
case NSURLErrorNotConnectedToInternet:
printError(title: "RESPONSE ERROR NO INTERNET", msg: "DESCRICAO: \(error.localizedDescription)")
case NSURLErrorNetworkConnectionLost:
printError(title: "RESPONSE ERROR CONNECTION LOST", msg: "DESCRICAO: \(error.localizedDescription)")
case NSURLErrorCancelledReasonUserForceQuitApplication:
printError(title: "RESPONSE ERROR APP QUIT", msg: "DESCRICAO: \(error.localizedDescription)")
case NSURLErrorCancelledReasonBackgroundUpdatesDisabled:
printError(title: "RESPONSE ERROR BG DISABLED", msg: "DESCRICAO: \(error.localizedDescription)")
case NSURLErrorBackgroundSessionWasDisconnected:
printError(title: "RESPONSE ERROR BG SESSION DISCONNECTED", msg: "DESCRICAO: \(error.localizedDescription)")
default:
printError(title: "GENERAL", msg: error.localizedDescription)
}
}else if let data = data, statusCode != 200 {
// - Check if is JSON result
if let jsonString = String(data: data, encoding: .utf8) {
printError(title: "JSON STATUS CODE \(statusCode)", msg: jsonString)
}else{
printError(title: "DATA STATUS CODE \(statusCode)", msg: data.debugDescription)
}
}
//
print("<======================================================================================>")
}
func mimeTypeForPath(path: String) -> String {
let url = URL(fileURLWithPath: path)
let pathExtension = url.pathExtension.lowercased()
// Dicionário de extensões e MIME types comuns
let mimeTypes: [String: String] = [
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"png": "image/png",
"gif": "image/gif",
"pdf": "application/pdf",
"txt": "text/plain",
"html": "text/html",
"htm": "text/html",
"json": "application/json",
"xml": "application/xml",
"zip": "application/zip",
"mp3": "audio/mpeg",
"mp4": "video/mp4",
"mov": "video/quicktime",
"doc": "application/msword",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"xls": "application/vnd.ms-excel",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"ppt": "application/vnd.ms-powerpoint",
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
]
// Retorna o MIME type correspondente à extensão, ou "application/octet-stream" como padrão
return mimeTypes[pathExtension] ?? "application/octet-stream"
}
}
#endif

View File

@@ -0,0 +1,602 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
import UIKit
import AVFoundation
#if os(watchOS)
import WatchKit
#endif
//import CommonCrypto
precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence }
infix operator ^^ : PowerPrecedence
public func ^^ (radix: Float, power: Float) -> Float {
return Float(pow(Double(radix), Double(power)))
}
/// Loverde Co: Custom Logs
public func printLog(
title: String,
msg: Any,
prettyPrint: Bool = false
){
if prettyPrint {
print(
"\n<========================= \(title) - START =========================>"
)
print(
msg
)
print(
"\n<========================= \(title) - END ===========================>"
)
}else{
print(
"\(title): \(msg)"
)
}
}
public func printInfo(
title: String,
msg: Any,
prettyPrint: Bool = false,
function: String = #function,
file: String = #file,
line: Int = #line,
column: Int = #column
){
if prettyPrint {
print(
"\n<========================= INFO: \(title) - START =========================>"
)
print(
"[\(file): FUNC: \(function): LINE: \(line) - COLUMN: \(column)]\n"
)
print(
msg
)
print(
"\n<========================= INFO: \(title) - END ===========================>"
)
}else{
print(
" INFO: \(title): \(msg)"
)
}
}
public func printWarn(
title: String,
msg: Any,
prettyPrint: Bool = false,
function: String = #function,
file: String = #file,
line: Int = #line,
column: Int = #column
){
if prettyPrint {
print(
"\n<========================= 🟡 WARN: \(title) - START =========================>"
)
print(
"[\(file): FUNC: \(function): LINE: \(line) - COLUMN: \(column)]\n"
)
print(
msg
)
print(
"\n<========================= 🟡 WARN: \(title) - END ===========================>"
)
}else{
print(
"🟡 WARN: \(title): \(msg)"
)
}
}
public func printError(
title: String,
msg: Any,
prettyPrint: Bool = false,
function: String = #function,
file: String = #file,
line: Int = #line,
column: Int = #column
){
if prettyPrint {
print(
"\n<========================= 🔴 ERROR: \(title) - START =========================>"
)
print(
"[\(file): FUNC: \(function): LINE: \(line) - COLUMN: \(column)]\n"
)
print(
msg
)
print(
"\n<========================= 🔴 ERROR: \(title) - END ===========================>"
)
}else{
print(
"🔴 ERROR: \(title): \(msg)"
)
}
}
public struct LCEssentials {
public static let DEFAULT_ERROR_DOMAIN = "LoverdeCoErrorDomain"
public static let DEFAULT_ERROR_CODE = -99
public static let DEFAULT_ERROR_MSG = "Error Unknow"
private static let cache = URLCache(
memoryCapacity: 50 * 1024 * 1024, // 50 MB em memória
diskCapacity: 200 * 1024 * 1024, // 200 MB em disco
diskPath: "file_cache"
)
#if os(iOS) || os(macOS)
/// Extract the file name from the file path
///
/// - Parameter filePath: Full file path in bundle
/// - Returns: File Name with extension
public static func sourceFileName(filePath: String) -> String {
let components = filePath.components(separatedBy: "/")
return components.isEmpty ? "" : components.last!
}
#endif
#if !os(macOS)
/// - LoverdeCo: App's name (if applicable).
@MainActor
public static var appDisplayName: String? {
return UIApplication.displayName
}
#endif
#if !os(macOS)
/// - LoverdeCo: App's bundle ID (if applicable).
public static var appBundleID: String? {
return Bundle.main.bundleIdentifier
}
#endif
#if !os(macOS)
/// - LoverdeCo: App current build number (if applicable).
@MainActor
public static var appBuild: String? {
return UIApplication.buildNumber
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Application icon badge current number.
@MainActor
public static var applicationIconBadgeNumber: Int {
get {
return UIApplication.shared.applicationIconBadgeNumber
}
set {
UIApplication.shared.applicationIconBadgeNumber = newValue
}
}
#endif
#if !os(macOS)
/// - LoverdeCo: App's current version (if applicable).
@MainActor
public static var appVersion: String? {
return UIApplication.version
}
#endif
#if os(iOS)
/// - LoverdeCo: Current battery level.
@MainActor
public static var batteryLevel: Float {
return UIDevice.current.batteryLevel
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Shared instance of current device.
@MainActor
public static var currentDevice: UIDevice {
return UIDevice.current
}
#elseif os(watchOS)
/// - LoverdeCo: Shared instance of current device.
public static var currentDevice: WKInterfaceDevice {
return WKInterfaceDevice.current()
}
#endif
#if !os(macOS)
/// - LoverdeCo: Screen height.
@MainActor
public static var screenHeight: CGFloat {
#if os(iOS) || os(tvOS)
return UIScreen.main.bounds.height
#elseif os(watchOS)
return currentDevice.screenBounds.height
#endif
}
#endif
#if os(iOS)
/// - LoverdeCo: Current orientation of device.
@MainActor
public static var deviceOrientation: UIDeviceOrientation {
return currentDevice.orientation
}
#endif
#if !os(macOS)
/// - LoverdeCo: Screen width.
@MainActor
public static var screenWidth: CGFloat {
#if os(iOS) || os(tvOS)
return UIScreen.main.bounds.width
#elseif os(watchOS)
return currentDevice.screenBounds.width
#endif
}
#endif
/// - LoverdeCo: Check if app is running in debug mode.
@MainActor
public static var isInDebuggingMode: Bool {
return UIApplication.inferredEnvironment == .debug
}
#if !os(macOS)
/// - LoverdeCo: Check if app is running in TestFlight mode.
@MainActor
public static var isInTestFlight: Bool {
return UIApplication.inferredEnvironment == .testFlight
}
#endif
#if os(iOS)
/// - LoverdeCo: Check if multitasking is supported in current device.
@MainActor
public static var isMultitaskingSupported: Bool {
return UIDevice.current.isMultitaskingSupported
}
#endif
#if os(iOS)
/// - LoverdeCo: Check if device is iPad.
@MainActor
public static var isPad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
#endif
#if os(iOS)
/// - LoverdeCo: Check if device is iPhone.
@MainActor
public static var isPhone: Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Check if device is registered for remote notifications for current app (read-only).
@MainActor
public static var isRegisteredForRemoteNotifications: Bool {
return UIApplication.shared.isRegisteredForRemoteNotifications
}
#endif
/// - LoverdeCo: Check if application is running on simulator (read-only).
@MainActor
public static var isRunningOnSimulator: Bool {
// http://stackoverflow.com/questions/24869481/detect-if-app-is-being-built-for-device-or-simulator-in-swift
#if targetEnvironment(simulator)
return true
#else
return false
#endif
}
#if os(iOS)
///- LoverdeCo: Status bar visibility state.
@MainActor
public static var isStatusBarHidden: Bool {
get {
if #available(iOS 13.0, *) {
let window = LCEssentials.keyWindow
return window?.windowScene?.statusBarManager?.isStatusBarHidden ?? true
} else {
return UIApplication.shared.isStatusBarHidden
}
}
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Key window (read only, if applicable).
@MainActor
public static var keyWindow: UIWindow? {
if #available(iOS 13.0, *) {
return UIApplication.shared.windows.filter {$0.isKeyWindow}.first
} else {
return UIApplication.shared.keyWindow
}
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Most top view controller (if applicable).
@MainActor
public static func getTopViewController(base: UIViewController? = nil, aboveBars: Bool = true) -> UIViewController? {
var viewController = base
if viewController == nil {
// iOS 13+ - Obter o rootViewController da UIWindow correta
if #available(iOS 13.0, *) {
// Acessa a janela ativa na cena principal
if let windowScene = UIApplication.shared.connectedScenes
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene,
let window = windowScene.windows.first(where: { $0.isKeyWindow }) {
viewController = window.rootViewController
}
} else {
// Pré-iOS 13
viewController = UIApplication.shared.keyWindow?.rootViewController
}
}
// Navegação - UINavigationController
if let nav = viewController as? UINavigationController {
if aboveBars {
return nav
}
return getTopViewController(base: nav.visibleViewController)
}
// Tabs - UITabBarController
if let tab = viewController as? UITabBarController,
let selected = tab.selectedViewController {
if aboveBars {
return tab
}
return getTopViewController(base: selected)
}
// Apresentações modais
if let presented = viewController?.presentedViewController {
return getTopViewController(base: presented)
}
return viewController
}
#endif
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Shared instance UIApplication.
@MainActor
public static var sharedApplication: UIApplication {
return UIApplication.shared
}
#endif
#if !os(macOS)
/// - LoverdeCo: System current version (read-only).
@MainActor
public static var systemVersion: String {
return currentDevice.systemVersion
}
#endif
public init(){}
}
// MARK: - Methods
public extension LCEssentials {
#if os(iOS) || os(macOS)
/// - LoverdeCo: Share link with message
///
/// - Parameters:
/// - message: String with message you whant to send
/// - url: String with url you want to share
@MainActor
static func shareApp(message: String = "", url: String = ""){
let textToShare = message
let root = self.getTopViewController(aboveBars: true)
var objectsToShare = [textToShare] as [Any]
if let myWebsite = NSURL(string: url) {
objectsToShare.append(myWebsite)
}
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = root?.view
root?.modalPresentationStyle = .fullScreen
root?.present(activityVC, animated: true, completion: nil)
}
/// - LoverdeCo: Make a call
///
/// - Parameters:
/// - number: string phone number
@MainActor
static func call(_ number: String!) {
UIApplication.openURL(urlStr: "tel://" + number)
}
/// - LoverdeCo: Open link on Safari
///
/// - Parameters:
/// - urlStr: url string to open
@MainActor
static func openSafari(_ urlStr: String){
UIApplication.openURL(urlStr: urlStr)
}
#endif
// MARK: - Background Thread
@MainActor
static func backgroundThread(delay: Double = 0.0, background: (@Sendable () -> Void)? = nil, completion: (@Sendable () -> Void)? = nil) {
DispatchQueue.global().async {
background?()
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
completion?()
}
}
}
@MainActor
static func dispatchAsync(completion: @escaping () -> Void) {
DispatchQueue.main.async {
completion()
}
}
/// - LoverdeCo: Delay function or closure call.
///
/// - Parameters:
/// - milliseconds: execute closure after the given delay.
/// - queue: a queue that completion closure should be executed on (default is DispatchQueue.main).
/// - completion: closure to be executed after delay.
/// - Returns: DispatchWorkItem task. You can call .cancel() on it to cancel delayed execution.
@discardableResult
static func delay(milliseconds: Double,
queue: DispatchQueue = .main,
completion: @escaping () -> Void) -> DispatchWorkItem {
let task = DispatchWorkItem { completion() }
queue.asyncAfter(deadline: .now() + (milliseconds/1000), execute: task)
return task
}
/// - LoverdeCo: Debounce function or closure call.
///
/// - Parameters:
/// - millisecondsOffset: allow execution of method if it was not called since millisecondsOffset.
/// - queue: a queue that action closure should be executed on (default is DispatchQueue.main).
/// - action: closure to be executed in a debounced way.
@MainActor
static func debounce(millisecondsDelay: Int,
queue: DispatchQueue = .main,
action: @escaping (() -> Void)) -> () -> Void {
// http://stackoverflow.com/questions/27116684/how-can-i-debounce-a-method-call
var lastFireTime = DispatchTime.now()
let dispatchDelay = DispatchTimeInterval.milliseconds(millisecondsDelay)
let dispatchTime: DispatchTime = lastFireTime + dispatchDelay
return {
queue.asyncAfter(deadline: dispatchTime) {
let when: DispatchTime = lastFireTime + dispatchDelay
let now = DispatchTime.now()
if now.rawValue >= when.rawValue {
lastFireTime = DispatchTime.now()
action()
}
}
}
}
#if os(iOS) || os(tvOS)
/// - LoverdeCo: Called when user takes a screenshot
///
/// - Parameter action: a closure to run when user takes a screenshot
@MainActor
static func didTakeScreenShot(_ action: @escaping (_ notification: Notification) -> Void) {
// http://stackoverflow.com/questions/13484516/ios-detection-of-screenshot
_ = NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification,
object: nil,
queue: OperationQueue.main) { notification in
action(notification)
}
}
#endif
#if os(iOS)
static func cachedFileURL(for url: URL) -> URL? {
let request = URLRequest(url: url)
guard let cachedResponse = cache.cachedResponse(for: request) else { return nil }
let tempDir = FileManager.default.temporaryDirectory
let tempFile = tempDir.appendingPathComponent(url.lastPathComponent)
do {
try cachedResponse.data.write(to: tempFile)
return tempFile
} catch {
return nil
}
}
@MainActor
static func downloadFileWithCache(from url: URL, completion: @escaping (Result<URL, Error>) -> Void) {
// Verifica se já existe no cache
if let cachedURL = cachedFileURL(for: url) {
completion(.success(cachedURL))
return
}
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, let response = response else {
completion(.failure(error ?? URLError(.badServerResponse)))
return
}
// Armazena no cache
let cachedResponse = CachedURLResponse(response: response, data: data)
cache.storeCachedResponse(cachedResponse, for: request)
// Escreve no diretório temporário
let tempDir = FileManager.default.temporaryDirectory
let tempFile = tempDir.appendingPathComponent(url.lastPathComponent)
do {
try data.write(to: tempFile)
completion(.success(tempFile))
} catch {
completion(.failure(error))
}
}
task.resume()
}
static func cleanExpiredCache(expiration: TimeInterval = 7 * 24 * 60 * 60) { // 1 semana padrão
cache.removeCachedResponses(since: Date().addingTimeInterval(-expiration))
// Limpa arquivos temporários antigos
let tempDir = FileManager.default.temporaryDirectory
do {
let files = try FileManager.default.contentsOfDirectory(at: tempDir, includingPropertiesForKeys: [.creationDateKey])
let expirationDate = Date().addingTimeInterval(-expiration)
for file in files {
let attributes = try FileManager.default.attributesOfItem(atPath: file.path)
if let creationDate = attributes[.creationDate] as? Date, creationDate < expirationDate {
try FileManager.default.removeItem(at: file)
}
}
} catch {
print("Erro ao limpar cache: \(error)")
}
}
#endif
}

View File

@@ -0,0 +1,27 @@
//
// Copyright (c) 2021 Loverde Co.
//
// 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
import UIKit
@objc public protocol LCESingletonDelegate: AnyObject {
@objc optional func singleton(object: Any?, withData: Any)
}

View File

@@ -0,0 +1,171 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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
import UIKit
// MARK: - Methods (Equatable)
public extension Array where Element: Equatable {
var unique: [Element] {
var uniqueValues: [Element] = []
forEach { item in
if !uniqueValues.contains(item) {
uniqueValues += [item]
}
}
return uniqueValues
}
/// Returns an array without nil elements.
var removeNilElements: [Element] {
return self.compactMap { $0 }
}
/// Remove all instances of an item from array.
///
/// [1, 2, 2, 3, 4, 5].removeAll(2) -> [1, 3, 4, 5]
/// ["h", "e", "l", "l", "o"].removeAll("l") -> ["h", "e", "o"]
///
/// - Parameter item: item to remove.
/// - Returns: self after removing all instances of item.
@discardableResult
mutating func removeAll(_ item: Element) -> [Element] {
removeAll(where: { $0 == item })
return self
}
/// Remove all instances contained in items parameter from array.
///
/// [1, 2, 2, 3, 4, 5].removeAll([2,5]) -> [1, 3, 4]
/// ["h", "e", "l", "l", "o"].removeAll(["l", "h"]) -> ["e", "o"]
///
/// - Parameter items: items to remove.
/// - Returns: self after removing all instances of all items in given array.
@discardableResult
mutating func removeAll(_ items: [Element]) -> [Element] {
guard !items.isEmpty else { return self }
removeAll(where: { items.contains($0) })
return self
}
/// Remove all duplicate elements from Array.
///
/// [1, 2, 2, 3, 4, 5].removeDuplicates() -> [1, 2, 3, 4, 5]
/// ["h", "e", "l", "l", "o"]. removeDuplicates() -> ["h", "e", "l", "o"]
///
/// - Returns: Return array with all duplicate elements removed.
@discardableResult
mutating func removeDuplicates() -> [Element] {
// Thanks to https://github.com/sairamkotha for improving the method
self = reduce(into: [Element]()) {
if !$0.contains($1) {
$0.append($1)
}
}
return self
}
/// Insert an element at the beginning of array.
///
/// [2, 3, 4, 5].prepend(1) -> [1, 2, 3, 4, 5]
/// ["e", "l", "l", "o"].prepend("h") -> ["h", "e", "l", "l", "o"]
///
/// - Parameter newElement: element to insert.
mutating func prepend(_ newElement: Element) {
insert(newElement, at: 0)
}
/// Safely swap values at given index positions.
///
/// [1, 2, 3, 4, 5].safeSwap(from: 3, to: 0) -> [4, 2, 3, 1, 5]
/// ["h", "e", "l", "l", "o"].safeSwap(from: 1, to: 0) -> ["e", "h", "l", "l", "o"]
///
/// - Parameters:
/// - index: index of first element.
/// - otherIndex: index of other element.
mutating func safeSwap(from index: Index, to otherIndex: Index) {
guard index != otherIndex else { return }
guard startIndex..<endIndex ~= index else { return }
guard startIndex..<endIndex ~= otherIndex else { return }
swapAt(index, otherIndex)
}
/// Return array with all duplicate elements removed.
///
/// [1, 1, 2, 2, 3, 3, 3, 4, 5].withoutDuplicates() -> [1, 2, 3, 4, 5])
/// ["h", "e", "l", "l", "o"].withoutDuplicates() -> ["h", "e", "l", "o"])
///
/// - Returns: an array of unique elements.
///
func withoutDuplicates() -> [Element] {
// Thanks to https://github.com/sairamkotha for improving the method
return reduce(into: [Element]()) {
if !$0.contains($1) {
$0.append($1)
}
}
}
/// Returns an array with all duplicate elements removed using KeyPath to compare.
///
/// - Parameter path: Key path to compare, the value must be Equatable.
/// - Returns: an array of unique elements.
func withoutDuplicates<E: Equatable>(keyPath path: KeyPath<Element, E>) -> [Element] {
return reduce(into: [Element]()) { (result, element) in
if !result.contains(where: { $0[keyPath: path] == element[keyPath: path] }) {
result.append(element)
}
}
}
/// Returns an array with all duplicate elements removed using KeyPath to compare.
///
/// - Parameter path: Key path to compare, the value must be Hashable.
/// - Returns: an array of unique elements.
func withoutDuplicates<E: Hashable>(keyPath path: KeyPath<Element, E>) -> [Element] {
var set = Set<E>()
return filter { set.insert($0[keyPath: path]).inserted }
}
}
extension Array where Element == NSLayoutConstraint {
@MainActor
func filtered(view: UIView, anchor: NSLayoutYAxisAnchor) -> [NSLayoutConstraint] {
return filter { constraint in
constraint.matches(view: view, anchor: anchor)
}
}
@MainActor
func filtered(view: UIView, anchor: NSLayoutXAxisAnchor) -> [NSLayoutConstraint] {
return filter { constraint in
constraint.matches(view: view, anchor: anchor)
}
}
@MainActor
func filtered(view: UIView, anchor: NSLayoutDimension) -> [NSLayoutConstraint] {
return filter { constraint in
constraint.matches(view: view, anchor: anchor)
}
}
}

View File

@@ -0,0 +1,50 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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
public extension BidirectionalCollection {
/// Returns the element at the specified position. If offset is negative, the `n`th element from the
/// end will be returned where `n` is the result of `abs(distance)`.
///
/// let arr = [1, 2, 3, 4, 5]
/// arr[offset: 1] -> 2
/// arr[offset: -2] -> 4
///
/// - Parameter distance: The distance to offset.
subscript(offset distance: Int) -> Element {
let index = distance >= 0 ? startIndex : endIndex
return self[indices.index(index, offsetBy: distance)]
}
/// Returns the last element of the sequence with having property by given key path equals to given
/// `value`.
///
/// - Parameters:
/// - keyPath: The `KeyPath` of property for `Element` to compare.
/// - value: The value to compare with `Element` property
/// - Returns: The last element of the collection that has property by given key path equals to given `value` or
/// `nil` if there is no such element.
func last<T: Equatable>(where keyPath: KeyPath<Element, T>, equals value: T) -> Element? {
return last { $0[keyPath: keyPath] == value }
}
}

View File

@@ -0,0 +1,51 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
#if canImport(Foundation)
import Foundation
// MARK: - Methods
public extension BinaryFloatingPoint {
#if canImport(Foundation)
/// Returns a rounded value with the specified number of decimal places and rounding rule. If
/// `numberOfDecimalPlaces` is negative, `0` will be used.
///
/// let num = 3.1415927
/// num.rounded(numberOfDecimalPlaces: 3, rule: .up) -> 3.142
/// num.rounded(numberOfDecimalPlaces: 3, rule: .down) -> 3.141
/// num.rounded(numberOfDecimalPlaces: 2, rule: .awayFromZero) -> 3.15
/// num.rounded(numberOfDecimalPlaces: 4, rule: .towardZero) -> 3.1415
/// num.rounded(numberOfDecimalPlaces: -1, rule: .toNearestOrEven) -> 3
///
/// - Parameters:
/// - numberOfDecimalPlaces: The expected number of decimal places.
/// - rule: The rounding rule to use.
/// - Returns: The rounded value.
func rounded(numberOfDecimalPlaces: Int, rule: FloatingPointRoundingRule) -> Self {
let factor = Self(pow(10.0, Double(max(0, numberOfDecimalPlaces))))
return (self * factor).rounded(rule) / factor
}
#endif
}
#endif

View File

@@ -0,0 +1,64 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
public extension BinaryInteger {
/// The raw bytes of the integer.
///
/// var number = Int16(-128)
/// print(number.bytes)
/// // prints "[255, 128]"
///
var bytes: [UInt8] {
var result = [UInt8]()
result.reserveCapacity(MemoryLayout<Self>.size)
var value = self
for _ in 0..<MemoryLayout<Self>.size {
result.append(UInt8(truncatingIfNeeded: value))
value >>= 8
}
return result.reversed()
}
}
// MARK: - Initializers
public extension BinaryInteger {
/// Creates a `BinaryInteger` from a raw byte representation.
///
/// var number = Int16(bytes: [0xFF, 0b1111_1101])
/// print(number!)
/// // prints "-3"
///
/// - Parameter bytes: An array of bytes representing the value of the integer.
init?(bytes: [UInt8]) {
// https://stackoverflow.com/a/43518567/9506784
precondition(bytes.count <= MemoryLayout<Self>.size,
"Integer with a \(bytes.count) byte binary representation of '\(bytes.map { String($0, radix: 2) }.joined(separator: " "))' overflows when stored into a \(MemoryLayout<Self>.size) byte '\(Self.self)'")
var value: Self = 0
for byte in bytes {
value <<= 8
value |= Self(byte)
}
self.init(exactly: value)
}
}

View File

@@ -0,0 +1,49 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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
// MARK: - Properties
public extension Bool {
/// Loverde Co: Return 1 if true, or 0 if false.
///
/// false.int -> 0
/// true.int -> 1
///
var int: Int {
return self ? 1 : 0
}
/// Loverde Co: Return "true" if true, or "false" if false.
///
/// false.string -> "false"
/// true.string -> "true"
///
var string: String {
return self ? "true" : "false"
}
var data: Data {
Data([self ? 1 : 0])
}
}

View File

@@ -0,0 +1,88 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
#if canImport(CoreGraphics)
import CoreGraphics
#if canImport(Foundation)
import Foundation
#endif
// MARK: - Properties
public extension CGFloat {
/// Absolute of CGFloat value.
var abs: CGFloat {
return Swift.abs(self)
}
#if canImport(Foundation)
/// Ceil of CGFloat value.
var ceil: CGFloat {
return Foundation.ceil(self)
}
#endif
/// Radian value of degree input.
var degreesToRadians: CGFloat {
return .pi * self / 180.0
}
#if canImport(Foundation)
/// Floor of CGFloat value.
var floor: CGFloat {
return Foundation.floor(self)
}
#endif
/// Check if CGFloat is positive.
var isPositive: Bool {
return self > 0
}
/// Check if CGFloat is negative.
var isNegative: Bool {
return self < 0
}
/// Int.
var int: Int {
return Int(self)
}
/// Float.
var float: Float {
return Float(self)
}
/// Double.
var double: Double {
return Double(self)
}
/// Degree value of radian input.
var radiansToDegrees: CGFloat {
return self * 180 / CGFloat.pi
}
}
#endif

View File

@@ -0,0 +1,37 @@
//
// Copyright (c) 2024 Loverde Co.
//
// 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
#if canImport(SwiftUI)
extension CGPoint: Hashable {
// Implementação manual de Hashable
public func hash(into hasher: inout Hasher) {
hasher.combine(x)
hasher.combine(y)
}
static func == (lhs: CGPoint, rhs: CGPoint) -> Bool {
return lhs.x == rhs.x && lhs.y == rhs.y
}
}
#endif

View File

@@ -0,0 +1,84 @@
//
// Copyright (c) 2024 Loverde Co.
//
// 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.
#if canImport(CoreGraphics)
import CoreGraphics
// MARK: - Properties
public extension CGRect {
/// Return center of rect.
var center: CGPoint { CGPoint(x: midX, y: midY) }
}
#if canImport(SwiftUI)
extension CGRect: Hashable {
// Implementação manual de Hashable
public func hash(into hasher: inout Hasher) {
hasher.combine(origin)
hasher.combine(size)
}
static func == (lhs: CGRect, rhs: CGRect) -> Bool {
return lhs.origin == rhs.origin && lhs.size == rhs.size
}
}
#endif
// MARK: - Initializers
public extension CGRect {
/// Create a `CGRect` instance with center and size.
/// - Parameters:
/// - center: center of the new rect.
/// - size: size of the new rect.
init(center: CGPoint, size: CGSize) {
let origin = CGPoint(x: center.x - size.width / 2.0, y: center.y - size.height / 2.0)
self.init(origin: origin, size: size)
}
}
// MARK: - Methods
public extension CGRect {
/// Create a new `CGRect` by resizing with specified anchor.
/// - Parameters:
/// - size: new size to be applied.
/// - anchor: specified anchor, a point in normalized coordinates -
/// '(0, 0)' is the top left corner of rect'(1, 1)' is the bottom right corner of rect,
/// defaults to '(0.5, 0.5)'. Example:
///
/// anchor = CGPoint(x: 0.0, y: 1.0):
///
/// A2------B2
/// A----B | |
/// | | --> | |
/// C----D C-------D2
///
func resizing(to size: CGSize, anchor: CGPoint = CGPoint(x: 0.5, y: 0.5)) -> CGRect {
let sizeDelta = CGSize(width: size.width - width, height: size.height - height)
return CGRect(origin: CGPoint(x: minX - sizeDelta.width * anchor.x,
y: minY - sizeDelta.height * anchor.y),
size: size)
}
}
#endif

View File

@@ -0,0 +1,285 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
#if canImport(CoreGraphics)
import CoreGraphics
// MARK: - Methods
public extension CGSize {
/// Returns the aspect ratio.
var aspectRatio: CGFloat {
guard height != 0 else { return 0 }
return width / height
}
/// Returns width or height, whichever is the bigger value.
var maxDimension: CGFloat {
return max(width, height)
}
/// Returns width or height, whichever is the smaller value.
var minDimension: CGFloat {
return min(width, height)
}
}
#if canImport(SwiftUI)
extension CGSize: @retroactive Hashable {
// Implementação manual de Hashable
public func hash(into hasher: inout Hasher) {
hasher.combine(width)
hasher.combine(height)
}
static func == (lhs: CGSize, rhs: CGSize) -> Bool {
return lhs.width == rhs.width && lhs.height == rhs.height
}
}
#endif
// MARK: - Methods
public extension CGSize {
/// Aspect fit CGSize.
///
/// let rect = CGSize(width: 120, height: 80)
/// let parentRect = CGSize(width: 100, height: 50)
/// let newRect = rect.aspectFit(to: parentRect)
/// // newRect.width = 75 , newRect = 50
///
/// - Parameter boundingSize: bounding size to fit self to.
/// - Returns: self fitted into given bounding size.
func aspectFit(to boundingSize: CGSize) -> CGSize {
let minRatio = min(boundingSize.width / width, boundingSize.height / height)
return CGSize(width: width * minRatio, height: height * minRatio)
}
/// Aspect fill CGSize.
///
/// let rect = CGSize(width: 20, height: 120)
/// let parentRect = CGSize(width: 100, height: 60)
/// let newRect = rect.aspectFit(to: parentRect)
/// // newRect.width = 100 , newRect = 60
///
/// - Parameter boundingSize: bounding size to fill self to.
/// - Returns: self filled into given bounding size.
func aspectFill(to boundingSize: CGSize) -> CGSize {
let minRatio = max(boundingSize.width / width, boundingSize.height / height)
let aWidth = min(width * minRatio, boundingSize.width)
let aHeight = min(height * minRatio, boundingSize.height)
return CGSize(width: aWidth, height: aHeight)
}
}
// MARK: - Operators
public extension CGSize {
/// Add two CGSize.
///
/// let sizeA = CGSize(width: 5, height: 10)
/// let sizeB = CGSize(width: 3, height: 4)
/// let result = sizeA + sizeB
/// // result = CGSize(width: 8, height: 14)
///
/// - Parameters:
/// - lhs: CGSize to add to.
/// - rhs: CGSize to add.
/// - Returns: The result comes from the addition of the two given CGSize struct.
static func + (lhs: CGSize, rhs: CGSize) -> CGSize {
return CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height)
}
/// Add a tuple to CGSize.
///
/// let sizeA = CGSize(width: 5, height: 10)
/// let result = sizeA + (5, 4)
/// // result = CGSize(width: 10, height: 14)
///
/// - Parameters:
/// - lhs: CGSize to add to.
/// - tuple: tuple value.
/// - Returns: The result comes from the addition of the given CGSize and tuple.
static func + (lhs: CGSize, tuple: (width: CGFloat, height: CGFloat)) -> CGSize {
return CGSize(width: lhs.width + tuple.width, height: lhs.height + tuple.height)
}
/// Add a CGSize to self.
///
/// var sizeA = CGSize(width: 5, height: 10)
/// let sizeB = CGSize(width: 3, height: 4)
/// sizeA += sizeB
/// // sizeA = CGPoint(width: 8, height: 14)
///
/// - Parameters:
/// - lhs: `self`.
/// - rhs: CGSize to add.
static func += (lhs: inout CGSize, rhs: CGSize) {
lhs.width += rhs.width
lhs.height += rhs.height
}
/// Add a tuple to self.
///
/// var sizeA = CGSize(width: 5, height: 10)
/// sizeA += (3, 4)
/// // result = CGSize(width: 8, height: 14)
///
/// - Parameters:
/// - lhs: `self`.
/// - tuple: tuple value.
static func += (lhs: inout CGSize, tuple: (width: CGFloat, height: CGFloat)) {
lhs.width += tuple.width
lhs.height += tuple.height
}
/// Subtract two CGSize.
///
/// let sizeA = CGSize(width: 5, height: 10)
/// let sizeB = CGSize(width: 3, height: 4)
/// let result = sizeA - sizeB
/// // result = CGSize(width: 2, height: 6)
///
/// - Parameters:
/// - lhs: CGSize to subtract from.
/// - rhs: CGSize to subtract.
/// - Returns: The result comes from the subtract of the two given CGSize struct.
static func - (lhs: CGSize, rhs: CGSize) -> CGSize {
return CGSize(width: lhs.width - rhs.width, height: lhs.height - rhs.height)
}
/// Subtract a tuple from CGSize.
///
/// let sizeA = CGSize(width: 5, height: 10)
/// let result = sizeA - (3, 2)
/// // result = CGSize(width: 2, height: 8)
///
/// - Parameters:
/// - lhs: CGSize to subtract from.
/// - tuple: tuple value.
/// - Returns: The result comes from the subtract of the given CGSize and tuple.
static func - (lhs: CGSize, tuple: (width: CGFloat, heoght: CGFloat)) -> CGSize {
return CGSize(width: lhs.width - tuple.width, height: lhs.height - tuple.heoght)
}
/// Subtract a CGSize from self.
///
/// var sizeA = CGSize(width: 5, height: 10)
/// let sizeB = CGSize(width: 3, height: 4)
/// sizeA -= sizeB
/// // sizeA = CGPoint(width: 2, height: 6)
///
/// - Parameters:
/// - lhs: `self`.
/// - rhs: CGSize to subtract.
static func -= (lhs: inout CGSize, rhs: CGSize) {
lhs.width -= rhs.width
lhs.height -= rhs.height
}
/// Subtract a tuple from self.
///
/// var sizeA = CGSize(width: 5, height: 10)
/// sizeA -= (2, 4)
/// // result = CGSize(width: 3, height: 6)
///
/// - Parameters:
/// - lhs: `self`.
/// - tuple: tuple value.
static func -= (lhs: inout CGSize, tuple: (width: CGFloat, height: CGFloat)) {
lhs.width -= tuple.width
lhs.height -= tuple.height
}
/// Multiply two CGSize.
///
/// let sizeA = CGSize(width: 5, height: 10)
/// let sizeB = CGSize(width: 3, height: 4)
/// let result = sizeA * sizeB
/// // result = CGSize(width: 15, height: 40)
///
/// - Parameters:
/// - lhs: CGSize to multiply.
/// - rhs: CGSize to multiply with.
/// - Returns: The result comes from the multiplication of the two given CGSize structs.
static func * (lhs: CGSize, rhs: CGSize) -> CGSize {
return CGSize(width: lhs.width * rhs.width, height: lhs.height * rhs.height)
}
/// Multiply a CGSize with a scalar.
///
/// let sizeA = CGSize(width: 5, height: 10)
/// let result = sizeA * 5
/// // result = CGSize(width: 25, height: 50)
///
/// - Parameters:
/// - lhs: CGSize to multiply.
/// - scalar: scalar value.
/// - Returns: The result comes from the multiplication of the given CGSize and scalar.
static func * (lhs: CGSize, scalar: CGFloat) -> CGSize {
return CGSize(width: lhs.width * scalar, height: lhs.height * scalar)
}
/// Multiply a CGSize with a scalar.
///
/// let sizeA = CGSize(width: 5, height: 10)
/// let result = 5 * sizeA
/// // result = CGSize(width: 25, height: 50)
///
/// - Parameters:
/// - scalar: scalar value.
/// - rhs: CGSize to multiply.
/// - Returns: The result comes from the multiplication of the given scalar and CGSize.
static func * (scalar: CGFloat, rhs: CGSize) -> CGSize {
return CGSize(width: scalar * rhs.width, height: scalar * rhs.height)
}
/// Multiply self with a CGSize.
///
/// var sizeA = CGSize(width: 5, height: 10)
/// let sizeB = CGSize(width: 3, height: 4)
/// sizeA *= sizeB
/// // result = CGSize(width: 15, height: 40)
///
/// - Parameters:
/// - lhs: `self`.
/// - rhs: CGSize to multiply.
static func *= (lhs: inout CGSize, rhs: CGSize) {
lhs.width *= rhs.width
lhs.height *= rhs.height
}
/// Multiply self with a scalar.
///
/// var sizeA = CGSize(width: 5, height: 10)
/// sizeA *= 3
/// // result = CGSize(width: 15, height: 30)
///
/// - Parameters:
/// - lhs: `self`.
/// - scalar: scalar value.
static func *= (lhs: inout CGSize, scalar: CGFloat) {
lhs.width *= scalar
lhs.height *= scalar
}
}
#endif

View File

@@ -0,0 +1,126 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
public extension Character {
/// Check if character is emoji.
///
/// Character("😀").isEmoji -> true
///
var isEmoji: Bool {
// http://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji
let scalarValue = String(self).unicodeScalars.first!.value
switch scalarValue {
case 0x1F600...0x1F64F, // Emoticons
0x1F300...0x1F5FF, // Misc Symbols and Pictographs
0x1F680...0x1F6FF, // Transport and Map
0x1F1E6...0x1F1FF, // Regional country flags
0x2600...0x26FF, // Misc symbols
0x2700...0x27BF, // Dingbats
0xE0020...0xE007F, // Tags
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
127_000...127_600, // Various asian characters
65024...65039, // Variation selector
9100...9300, // Misc items
8400...8447: // Combining Diacritical Marks for Symbols
return true
default:
return false
}
}
/// Integer from character (if applicable).
///
/// Character("1").int -> 1
/// Character("A").int -> nil
///
var int: Int? {
return Int(String(self))
}
/// String from character.
///
/// Character("a").string -> "a"
///
var string: String {
return String(self)
}
/// Return the character lowercased.
///
/// Character("A").lowercased -> Character("a")
///
var lowercased: Character {
return String(self).lowercased().first!
}
/// Return the character uppercased.
///
/// Character("a").uppercased -> Character("A")
///
var uppercased: Character {
return String(self).uppercased().first!
}
}
// MARK: - Methods
public extension Character {
/// Random character.
///
/// Character.random() -> k
///
/// - Returns: A random character.
static func randomAlphanumeric() -> Character {
return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".randomElement()!
}
}
// MARK: - Operators
public extension Character {
/// Repeat character multiple times.
///
/// Character("-") * 10 -> "----------"
///
/// - Parameters:
/// - lhs: character to repeat.
/// - rhs: number of times to repeat character.
/// - Returns: string with character repeated n times.
static func * (lhs: Character, rhs: Int) -> String {
guard rhs > 0 else { return "" }
return String(repeating: String(lhs), count: rhs)
}
/// Repeat character multiple times.
///
/// 10 * Character("-") -> "----------"
///
/// - Parameters:
/// - lhs: number of times to repeat character.
/// - rhs: character to repeat.
/// - Returns: string with character repeated n times.
static func * (lhs: Int, rhs: Character) -> String {
guard lhs > 0 else { return "" }
return String(repeating: String(rhs), count: lhs)
}
}

View File

@@ -0,0 +1,193 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
#if canImport(Dispatch)
import Dispatch
#endif
// MARK: - Properties
public extension Collection {
/// The full range of the collection.
var fullRange: Range<Index> { startIndex..<endIndex }
}
// MARK: - Methods
public extension Collection {
#if canImport(Dispatch)
/// Performs `each` closure for each element of collection in parallel.
///
/// array.forEachInParallel { item in
/// print(item)
/// }
///
/// - Parameter each: closure to run for each element.
func forEachInParallel(_ each: (Self.Element) -> Void) {
DispatchQueue.concurrentPerform(iterations: count) {
each(self[index(startIndex, offsetBy: $0)])
}
}
#endif
/// Safe protects the array from out of bounds by use of optional.
///
/// let arr = [1, 2, 3, 4, 5]
/// arr[safe: 1] -> 2
/// arr[safe: 10] -> nil
///
/// - Parameter index: index of element to access element.
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
/// Returns an array of slices of length "size" from the array. If array can't be split evenly, the
/// final slice will be the remaining elements.
///
/// [0, 2, 4, 7].group(by: 2) -> [[0, 2], [4, 7]]
/// [0, 2, 4, 7, 6].group(by: 2) -> [[0, 2], [4, 7], [6]]
///
/// - Parameter size: The size of the slices to be returned.
/// - Returns: grouped self.
func group(by size: Int) -> [[Element]]? {
// Inspired by: https://lodash.com/docs/4.17.4#chunk
guard size > 0, !isEmpty else { return nil }
var start = startIndex
var slices = [[Element]]()
while start != endIndex {
let end = index(start, offsetBy: size, limitedBy: endIndex) ?? endIndex
slices.append(Array(self[start..<end]))
start = end
}
return slices
}
/// Get all indices where condition is met.
///
/// [1, 7, 1, 2, 4, 1, 8].indices(where: { $0 == 1 }) -> [0, 2, 5]
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: all indices where the specified condition evaluates to true (optional).
func indices(where condition: (Element) throws -> Bool) rethrows -> [Index]? {
let indices = try self.indices.filter { try condition(self[$0]) }
return indices.isEmpty ? nil : indices
}
/// Calls the given closure with an array of size of the parameter slice.
///
/// [0, 2, 4, 7].forEach(slice: 2) { print($0) } -> // print: [0, 2], [4, 7]
/// [0, 2, 4, 7, 6].forEach(slice: 2) { print($0) } -> // print: [0, 2], [4, 7], [6]
///
/// - Parameters:
/// - slice: size of array in each interation.
/// - body: a closure that takes an array of slice size as a parameter.
func forEach(slice: Int, body: ([Element]) throws -> Void) rethrows {
var start = startIndex
while case let end = index(start, offsetBy: slice, limitedBy: endIndex) ?? endIndex,
start != end {
try body(Array(self[start..<end]))
start = end
}
}
/// Unique pair of elements in a collection.
///
/// let array = [1, 2, 3]
/// for (first, second) in array.adjacentPairs() {
/// print(first, second) // print: (1, 2) (1, 3) (2, 3)
/// }
///
///
/// - Returns: a sequence of adjacent pairs of elements from this collection.
func adjacentPairs() -> AnySequence<(Element, Element)> {
guard var index1 = index(startIndex, offsetBy: 0, limitedBy: endIndex),
var index2 = index(index1, offsetBy: 1, limitedBy: endIndex) else {
return AnySequence {
EmptyCollection.Iterator()
}
}
return AnySequence {
AnyIterator {
if index1 >= endIndex || index2 >= endIndex {
return nil
}
defer {
index2 = self.index(after: index2)
if index2 >= endIndex {
index1 = self.index(after: index1)
index2 = self.index(after: index1)
}
}
return (self[index1], self[index2])
}
}
}
}
public extension Collection where Indices.Iterator.Element == Index {
subscript (exist index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
// MARK: - Methods (Equatable)
public extension Collection where Element: Equatable {
/// All indices of specified item.
///
/// [1, 2, 2, 3, 4, 2, 5].indices(of 2) -> [1, 2, 5]
/// [1.2, 2.3, 4.5, 3.4, 4.5].indices(of 2.3) -> [1]
/// ["h", "e", "l", "l", "o"].indices(of "l") -> [2, 3]
///
/// - Parameter item: item to check.
/// - Returns: an array with all indices of the given item.
func indices(of item: Element) -> [Index] {
return indices.filter { self[$0] == item }
}
}
// MARK: - Methods (BinaryInteger)
public extension Collection where Element: BinaryInteger {
/// Average of all elements in array.
///
/// - Returns: the average of the array's elements.
func average() -> Double {
// http://stackoverflow.com/questions/28288148/making-my-function-calculate-average-of-array-swift
guard !isEmpty else { return .zero }
return Double(reduce(.zero, +)) / Double(count)
}
}
// MARK: - Methods (FloatingPoint)
public extension Collection where Element: FloatingPoint {
/// Average of all elements in array.
///
/// [1.2, 2.3, 4.5, 3.4, 4.5].average() = 3.18
///
/// - Returns: average of the array's elements.
func average() -> Element {
guard !isEmpty else { return .zero }
return reduce(.zero, +) / Element(count)
}
}

View File

@@ -0,0 +1,50 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
public extension Comparable {
/// Returns true if value is in the provided range.
///
/// 1.isBetween(5...7) // false
/// 7.isBetween(6...12) // true
/// date.isBetween(date1...date2)
/// "c".isBetween(a...d) // true
/// 0.32.isBetween(0.31...0.33) // true
///
/// - Parameter range: Closed range against which the value is checked to be included.
/// - Returns: `true` if the value is included in the range, `false` otherwise.
func isBetween(_ range: ClosedRange<Self>) -> Bool {
return range ~= self
}
/// Returns value limited within the provided range.
///
/// 1.clamped(to: 3...8) // 3
/// 4.clamped(to: 3...7) // 4
/// "c".clamped(to: "e"..."g") // "e"
/// 0.32.clamped(to: 0.1...0.29) // 0.29
///
/// - Parameter range: Closed range that limits the value.
/// - Returns: A value limited to the range, i.e. between `range.lowerBound` and `range.upperBound`.
func clamped(to range: ClosedRange<Self>) -> Self {
return max(range.lowerBound, min(self, range.upperBound))
}
}

View File

@@ -0,0 +1,107 @@
//
// Copyright (c) 2025 Loverde Co.
//
// 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
//import CryptoKit
//
//struct AESUtils {
//
// // MARK: - Métodos Existente (AES Key Generation)
// static func generateAESKeyFrom(_ characters: String) -> String {
// let binaryData = Data(characters.utf8)
// let hexString = binaryData.map { String(format: "%02hhx", $0) }.joined()
// return Data(hexString.utf8).base64EncodedString()
// }
//
// static func degenerateAESKeyFrom(_ base64Encoded: String) -> String? {
// guard let debaseData = Data(base64Encoded: base64Encoded),
// let hexString = String(data: debaseData, encoding: .utf8) else {
// return nil
// }
// var binaryData = Data()
// for i in stride(from: 0, to: hexString.count, by: 2) {
// let start = hexString.index(hexString.startIndex, offsetBy: i)
// let end = hexString.index(start, offsetBy: 2, limitedBy: hexString.endIndex) ?? hexString.endIndex
// let byteString = String(hexString[start..<end])
// if let byte = UInt8(byteString, radix: 16) {
// binaryData.append(byte)
// }
// }
// return String(data: binaryData, encoding: .utf8)
// }
//
// // MARK: - Métodos OTP (Novos)
// static func generateOTPEncodedKey(from originalBase64: String) -> String? {
// guard let originalData = originalBase64.data(using: .utf8) else { return nil }
//
// // Gera um pad (OTP) aleatório do mesmo tamanho que originalData
// var pad = Data(count: originalData.count)
// let result = pad.withUnsafeMutableBytes {
// SecRandomCopyBytes(kSecRandomDefault, originalData.count, $0.baseAddress!)
// }
// guard result == errSecSuccess else { return nil }
//
// // Aplica XOR entre originalData e pad
// let xorResult = zip(originalData, pad).map { $0 ^ $1 }
//
// // Combina pad + xorResult e codifica em Base64
// let combinedData = pad + Data(xorResult)
// return combinedData.base64EncodedString()
// }
//
// static func verifyOTPAndDecode(_ otpEncoded: String) -> String? {
// guard let combinedData = Data(base64Encoded: otpEncoded) else { return nil }
//
// // Divide pad e xorResult (metade para cada)
// let halfLength = combinedData.count / 2
// guard halfLength > 0 else { return nil }
//
// let pad = combinedData[0..<halfLength]
// let xorResult = combinedData[halfLength..<combinedData.count]
//
// // Aplica XOR novamente para obter o original
// let originalData = zip(pad, xorResult).map { $0 ^ $1 }
//
// return String(data: Data(originalData), encoding: .utf8)
// }
//}
//// 1. Gera a chave AES original (como exemplo)
//let originalKey = "chave_secreta"
//let base64Original = AESUtils.generateAESKeyFrom(originalKey) // "NjEyMzQ1Njc4OQ=="
//
//// 2. Gera a versão OTP (diferente a cada execução)
//if let otpEncoded = AESUtils.generateOTPEncodedKey(from: base64Original) {
// print("OTP Encoded (Swift): \(otpEncoded)") // Ex: "qKior6mqq6ytrq+vsLGys7S1tre4ubq7vL2+v8DBwsPExcY="
//
// // 3. Decodifica o OTP para obter o Base64 original
// if let decodedBase64 = AESUtils.verifyOTPAndDecode(otpEncoded) {
// print("Decoded Base64 (Swift): \(decodedBase64)") // Deve ser igual a base64Original
//
// // 4. Degenera a chave original
// if let originalKeyDecoded = AESUtils.degenerateAESKeyFrom(decodedBase64) {
// print("Chave Original Recuperada (Swift): \(originalKeyDecoded)") // "chave_secreta"
// }
// }
//}

View File

@@ -0,0 +1,132 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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
import CryptoKit
import CommonCrypto
public extension Data {
var prettyJson: String? {
guard let object = try? JSONSerialization.jsonObject(with: self, options: []),
let data = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted]),
let prettyPrintedString = String(data: data, encoding:.utf8) else { return nil }
return prettyPrintedString
}
var toDictionay: Dictionary<String, Any>? {
do {
return try JSONSerialization.jsonObject(with: self, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
return nil
}
}
var toHexString: String {
String(self.map { String(format: "%02hhx", $0) }.joined())
}
var bool: Bool {
first != 0
}
init?(hexString: String) {
let cleanHex = hexString.replacingOccurrences(of: " ", with: "")
let length = cleanHex.count / 2
var data = Data(capacity: length)
for i in 0..<length {
let start = cleanHex.index(cleanHex.startIndex, offsetBy: i*2)
let end = cleanHex.index(start, offsetBy: 2)
guard let byte = UInt8(cleanHex[start..<end], radix: 16) else {
return nil
}
data.append(byte)
}
self = data
}
@available(iOS 13.0, *)
func HMACSHA512(key: Data) -> Data {
var hmac = HMAC<SHA512>.init(key: SymmetricKey(data: key))
hmac.update(data: self)
return Data(hmac.finalize())
}
func SHA512() -> Data {
var digest = [UInt8](repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH))
self.withUnsafeBytes {
_ = CC_SHA512($0.baseAddress, CC_LONG(self.count), &digest)
}
return Data(digest)
}
func XOR(with other: Data) -> Data {
return Data(zip(self, other).map { $0 ^ $1 })
}
func SHA256() -> Data {
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
self.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(self.count), &digest)
}
return Data(digest)
}
func object<T: Codable>() -> T? {
do {
let outPut: T = try JSONDecoder.decode(data: self)
return outPut
} catch {
printError(title: "DATA DECODE ERROR", msg: error.localizedDescription, prettyPrint: true)
return nil
}
}
func toHexadecimalString() -> String {
return `lazy`.reduce("") {
var s = String($1, radix: 16)
if s.count == 1 {
s = "0" + s
}
return $0 + s
}
}
///Loverde Co.: MD5 - Data
/////Test:
///let md5Data = Data.MD5(string:"Hello")
///
///let md5Hex = md5Data.toHexString()
///print("md5Hex: \(md5Hex)")
@available(iOS 13.0, *)
static func MD5(string: String) -> Data {
let messageData = string.data(using: .utf8)!
let digestData = Insecure.MD5.hash (data: messageData)
let digestHex = String(digestData.map { String(format: "%02hhx", $0) }.joined().prefix(32))
return Data(digestHex.utf8)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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
public extension Decimal {
mutating func round(_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode) {
var localCopy = self
NSDecimalRound(&self, &localCopy, scale, roundingMode)
}
func rounded(_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode) -> Decimal {
var result = Decimal()
var localCopy = self
NSDecimalRound(&result, &localCopy, scale, roundingMode)
return result
}
}

View File

@@ -0,0 +1,324 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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
import UIKit
public extension Dictionary {
/// Deep fetch or set a value from nested dictionaries.
///
/// var dict = ["key": ["key1": ["key2": "value"]]]
/// dict[path: ["key", "key1", "key2"]] = "newValue"
/// dict[path: ["key", "key1", "key2"]] -> "newValue"
///
/// - Note: Value fetching is iterative, while setting is recursive.
///
/// - Complexity: O(N), _N_ being the length of the path passed in.
///
/// - Parameter path: An array of keys to the desired value.
///
/// - Returns: The value for the key-path passed in. `nil` if no value is found.
subscript(path path: [Key]) -> Any? {
get {
guard !path.isEmpty else { return nil }
var result: Any? = self
for key in path {
if let element = (result as? [Key: Any])?[key] {
result = element
} else {
return nil
}
}
return result
}
set {
if let first = path.first {
if path.count == 1, let new = newValue as? Value {
return self[first] = new
}
if var nested = self[first] as? [Key: Any] {
nested[path: Array(path.dropFirst())] = newValue
return self[first] = nested as? Value
}
}
}
}
var queryString: String {
var output: String = ""
for (key,value) in self {
output += "\(key)=\(value)&"
}
output = String(output.dropLast())
return output
}
var convertToJSON: String {
do {
let jsonData = try JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions.prettyPrinted)
if let jsonString = NSString(data: jsonData, encoding: String.Encoding.utf8.rawValue) as? String {
return jsonString
}
return "Data to String with UFT8 Encoded error parsing"
} catch {
return "\(error.localizedDescription)"
}
}
/// Creates a Dictionary from a given sequence grouped by a given key path.
///
/// - Parameters:
/// - sequence: Sequence being grouped.
/// - keypath: The key path to group by.
init<S: Sequence>(grouping sequence: S, by keyPath: KeyPath<S.Element, Key>) where Value == [S.Element] {
self.init(grouping: sequence, by: { $0[keyPath: keyPath] })
}
//MARK: - Append Dictionary
static func += (lhs: inout [Key: Value], rhs: [Key: Value]) {
rhs.forEach { lhs[$0] = $1}
}
/// Remove keys contained in the sequence from the dictionary
///
/// let dict: [String: String] = ["key1": "value1", "key2": "value2", "key3": "value3"]
/// let result = dict-["key1", "key2"]
/// result.keys.contains("key3") -> true
/// result.keys.contains("key1") -> false
/// result.keys.contains("key2") -> false
///
/// - Parameters:
/// - lhs: dictionary
/// - rhs: array with the keys to be removed.
/// - Returns: a new dictionary with keys removed.
static func - <S: Sequence>(lhs: [Key: Value], keys: S) -> [Key: Value] where S.Element == Key {
var result = lhs
result.removeAll(keys: keys)
return result
}
/// Remove keys contained in the sequence from the dictionary
///
/// var dict: [String: String] = ["key1": "value1", "key2": "value2", "key3": "value3"]
/// dict-=["key1", "key2"]
/// dict.keys.contains("key3") -> true
/// dict.keys.contains("key1") -> false
/// dict.keys.contains("key2") -> false
///
/// - Parameters:
/// - lhs: dictionary
/// - rhs: array with the keys to be removed.
static func -= <S: Sequence>(lhs: inout [Key: Value], keys: S) where S.Element == Key {
lhs.removeAll(keys: keys)
}
/// - LoverdeCo: Convert Dictonary to Object
///
/// - returns: Object: Codable/Decodable
func toObjetct<T: Codable>() -> T {
let jsonString = self.convertToJSON
let output: T = try! JSONDecoder.decode(jsonString)
return output
}
/// Check if key exists in dictionary.
///
/// let dict: [String: Any] = ["testKey": "testValue", "testArrayKey": [1, 2, 3, 4, 5]]
/// dict.has(key: "testKey") -> true
/// dict.has(key: "anotherKey") -> false
///
/// - Parameter key: key to search for
/// - Returns: true if key exists in dictionary.
func has(key: Key) -> Bool {
return index(forKey: key) != nil
}
/// Remove all keys contained in the keys parameter from the dictionary.
///
/// var dict : [String: String] = ["key1" : "value1", "key2" : "value2", "key3" : "value3"]
/// dict.removeAll(keys: ["key1", "key2"])
/// dict.keys.contains("key3") -> true
/// dict.keys.contains("key1") -> false
/// dict.keys.contains("key2") -> false
///
/// - Parameter keys: keys to be removed
mutating func removeAll<S: Sequence>(keys: S) where S.Element == Key {
keys.forEach { removeValue(forKey: $0) }
}
/// Remove a value for a random key from the dictionary.
@discardableResult
mutating func removeValueForRandomKey() -> Value? {
guard let randomKey = keys.randomElement() else { return nil }
return removeValue(forKey: randomKey)
}
#if canImport(Foundation)
/// JSON Data from dictionary.
///
/// - Parameter prettify: set true to prettify data (default is false).
/// - Returns: optional JSON Data (if applicable).
func jsonData(prettify: Bool = false) -> Data? {
guard JSONSerialization.isValidJSONObject(self) else {
return nil
}
let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization
.WritingOptions()
return try? JSONSerialization.data(withJSONObject: self, options: options)
}
#endif
#if canImport(Foundation)
/// JSON String from dictionary.
///
/// dict.jsonString() -> "{"testKey":"testValue","testArrayKey":[1,2,3,4,5]}"
///
/// dict.jsonString(prettify: true)
/// /*
/// returns the following string:
///
/// "{
/// "testKey" : "testValue",
/// "testArrayKey" : [
/// 1,
/// 2,
/// 3,
/// 4,
/// 5
/// ]
/// }"
///
/// */
///
/// - Parameter prettify: set true to prettify string (default is false).
/// - Returns: optional JSON String (if applicable).
func jsonString(prettify: Bool = false) -> String? {
guard JSONSerialization.isValidJSONObject(self) else { return nil }
let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization
.WritingOptions()
guard let jsonData = try? JSONSerialization.data(withJSONObject: self, options: options) else { return nil }
return String(data: jsonData, encoding: .utf8)
}
#endif
/// Returns a dictionary containing the results of mapping the given closure over the sequences
/// elements.
/// - Parameter transform: A mapping closure. `transform` accepts an element of this sequence as its parameter and
/// returns a transformed value of the same or of a different type.
/// - Returns: A dictionary containing the transformed elements of this sequence.
func mapKeysAndValues<K, V>(_ transform: ((key: Key, value: Value)) throws -> (K, V)) rethrows -> [K: V] {
return try [K: V](uniqueKeysWithValues: map(transform))
}
/// Returns a dictionary containing the non-`nil` results of calling the given transformation with
/// each element of this sequence.
/// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an
/// optional value.
/// - Returns: A dictionary of the non-`nil` results of calling `transform` with each element of the sequence.
/// - Complexity: *O(m + n)*, where _m_ is the length of this sequence and _n_ is the length of the result.
func compactMapKeysAndValues<K, V>(_ transform: ((key: Key, value: Value)) throws -> (K, V)?) rethrows -> [K: V] {
return try [K: V](uniqueKeysWithValues: compactMap(transform))
}
/// Creates a new dictionary using specified keys.
///
/// var dict = ["key1": 1, "key2": 2, "key3": 3, "key4": 4]
/// dict.pick(keys: ["key1", "key3", "key4"]) -> ["key1": 1, "key3": 3, "key4": 4]
/// dict.pick(keys: ["key2"]) -> ["key2": 2]
///
/// - Complexity: O(K), where _K_ is the length of the keys array.
///
/// - Parameter keys: An array of keys that will be the entries in the resulting dictionary.
///
/// - Returns: A new dictionary that contains the specified keys only. If none of the keys exist, an empty
/// dictionary will be returned.
func pick(keys: [Key]) -> [Key: Value] {
keys.reduce(into: [Key: Value]()) { result, item in
result[item] = self[item]
}
}
/// Merge the keys/values of two dictionaries.
///
/// let dict: [String: String] = ["key1": "value1"]
/// let dict2: [String: String] = ["key2": "value2"]
/// let result = dict + dict2
/// result["key1"] -> "value1"
/// result["key2"] -> "value2"
///
/// - Parameters:
/// - lhs: dictionary.
/// - rhs: dictionary.
/// - Returns: An dictionary with keys and values from both.
static func + (lhs: [Key: Value], rhs: [Key: Value]) -> [Key: Value] {
var result = lhs
rhs.forEach { result[$0] = $1 }
return result
}
}
// MARK: - Methods (Value: Equatable)
public extension Dictionary where Value: Equatable {
/// Returns an array of all keys that have the given value in dictionary.
///
/// let dict = ["key1": "value1", "key2": "value1", "key3": "value2"]
/// dict.keys(forValue: "value1") -> ["key1", "key2"]
/// dict.keys(forValue: "value2") -> ["key3"]
/// dict.keys(forValue: "value3") -> []
///
/// - Parameter value: Value for which keys are to be fetched.
/// - Returns: An array containing keys that have the given value.
func keys(forValue value: Value) -> [Key] {
return keys.filter { self[$0] == value }
}
}
// MARK: - Methods (ExpressibleByStringLiteral)
public extension Dictionary where Key: StringProtocol {
/// Lowercase all keys in dictionary.
///
/// var dict = ["tEstKeY": "value"]
/// dict.lowercaseAllKeys()
/// print(dict) // prints "["testkey": "value"]"
///
mutating func lowercaseAllKeys() {
// http://stackoverflow.com/questions/33180028/extend-dictionary-where-key-is-of-type-string
for key in keys {
if let lowercaseKey = String(describing: key).lowercased() as? Key {
self[lowercaseKey] = removeValue(forKey: key)
}
}
}
}
public extension Dictionary where Value: Hashable {
var uniqueValues: Dictionary<Key, Value> {
var seenValues = Set<Value>()
return self.filter { seenValues.insert($0.value).inserted }
}
}

View File

@@ -0,0 +1,77 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
#if canImport(CoreGraphics)
import CoreGraphics
#endif
#if os(macOS) || os(iOS)
import Darwin
#elseif os(Linux)
import Glibc
#endif
// MARK: - Properties
public extension Double {
var int: Int {
return Int(self)
}
var float: Float {
return Float(self)
}
#if canImport(CoreGraphics)
var cgFloat: CGFloat {
return CGFloat(self)
}
#endif
var satsToBTC: Double {
return (self / bitcoinIntConvertion.double)
}
var convertToBTC: Double {
return satsToBTC
}
var toBTC: Double {
return satsToBTC
}
}
// MARK: - Operators
precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence }
infix operator **: PowerPrecedence
/// Value of exponentiation.
///
/// - Parameters:
/// - lhs: base double.
/// - rhs: exponent double.
/// - Returns: exponentiation result (example: 4.4 ** 0.5 = 2.0976176963).
public func ** (lhs: Double, rhs: Double) -> Double {
// http://nshipster.com/swift-operators/
return pow(lhs, rhs)
}

View File

@@ -0,0 +1,104 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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
//MARK: - Codables convertions
public extension Encodable {
subscript(key: String) -> Any? {
return dictionary[key]
}
var dictionary: [String: Any] {
return (try? JSONSerialization.jsonObject(with: JSONEncoder().encode(self))) as? [String: Any] ?? [:]
}
var json: String {
return self.dictionary.convertToJSON
}
var data: Data {
return self.json.data
}
}
extension JSONDecoder {
/// - LoverdeCo: Decode JSON Data to Object
///
/// - Parameter data: Data
/// - returns: Object: Codable/Decodable
public static func decode<T: Codable>(data: Data) throws -> T {
var error = NSError(domain: "", code: 0)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .useDefaultKeys
do {
return try decoder.decode(T.self, from: data)
} catch let DecodingError.keyNotFound(key, context) {
let msg = "Missing key '\(key.stringValue)' in \(T.self) Object in JSON: \(context.debugDescription)"
error = NSError.createErrorWith(code: 0, description: msg, reasonForError: msg)
} catch let DecodingError.typeMismatch(type, context) {
let msg = "Type mismatch for type '\(type)' \(T.self) Object: \(context.debugDescription)"
error = NSError.createErrorWith(code: 0, description: msg, reasonForError: msg)
} catch let DecodingError.valueNotFound(value, context) {
let msg = "Missing value '\(value)' in \(T.self) Object in JSON: \(context.debugDescription)"
error = NSError.createErrorWith(code: 0, description: msg, reasonForError: msg)
} catch {
throw error
}
throw error
}
/// - LoverdeCo: Decode JSON String to Object
///
/// - Parameter json: String
/// - returns: Object: Codable/Decodable
public static func decode<T: Codable>(_ json: String, using encoding: String.Encoding = .utf8) throws -> T {
var error = NSError()
if let jsonData = json.data(using: .utf8) {
do {
return try decode(data: jsonData)
} catch {
throw error
}
}
throw error
}
/// - LoverdeCo: Decode JSON URL to Object
///
/// - Parameter url: URL
/// - returns: Object: Codable/Decodable
public static func decode<T: Codable>(fromURL url: URL) throws -> T {
return try decode(data: try! Data(contentsOf: url))
}
public static func decode<T: Codable>(dictionary: Any) throws -> T {
do {
let json = try JSONSerialization.data(withJSONObject: dictionary)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(T.self, from: json)
} catch {
printError(title: "JSONDecoder.decode<T: Codable>", msg: error, prettyPrint: true)
throw error
}
}
}

View File

@@ -0,0 +1,128 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
import UIKit
public extension FileManager {
func createDirectory(_ directoryName: String) -> URL? {
let fileManager = FileManager.default
if let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
let filePath = documentDirectory.appendingPathComponent(directoryName)
if !fileManager.fileExists(atPath: filePath.path) {
do {
try fileManager.createDirectory(atPath: filePath.path, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error.localizedDescription)
return nil
}
}
return filePath
} else {
return nil
}
}
func saveFileToDirectory( _ sourceURL: URL, toPathURL: URL ) -> Bool {
do {
try FileManager.default.moveItem(at: sourceURL, to: toPathURL)
return true
} catch let error as NSError {
print("Erro ao salvar o arquivo: \(error.localizedDescription)")
return false
}
}
#if os(iOS) || os(macOS)
func saveImageToDirectory( _ imageWithPath : String, imagem : UIImage ) -> Bool {
let data = imagem.pngData()
let success = (try? data!.write(to: URL(fileURLWithPath: imageWithPath), options: [])) != nil
//let success = NSFileManager.defaultManager().createFileAtPath(imageWithPath, contents: data, attributes: nil)
if success {
return true
} else {
NSLog("Unable to create directory")
return false
}
}
#endif
func retrieveFile( _ directoryAndFile: String ) -> URL {
let documentsPath = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let logsPath = documentsPath.appendingPathComponent(directoryAndFile)
return logsPath
}
func removeFile( _ directoryAndFile: String ) -> Bool {
do {
try self.removeItem(atPath: directoryAndFile)
return true
}
catch let error as NSError {
print("Ooops! Something went wrong: \(error)")
return false
}
}
/// LoverdeCo: Retrieve all files names as dictionary.
///
/// - Parameters:
/// - directoryName: Give a directory name.
/// - Returns:
/// An array of files name: [String].
func retrieveAllFilesFromDirectory(directoryName: String) -> [String]? {
let fileMngr = FileManager.default;
let docs = fileMngr.urls(for: .documentDirectory, in: .userDomainMask)[0].path
do {
var filelist = try fileMngr.contentsOfDirectory(atPath: "\(docs)/\(directoryName)")
if filelist.contains(".DS_Store") {
filelist.remove(at: filelist.firstIndex(of: ".DS_Store")!)
}
return filelist
} catch let error {
print("Error: \(error.localizedDescription)")
return nil
}
}
func directoryExistsAtPath(_ path: String) -> Bool {
var isDirectory = ObjCBool(true)
let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
return exists && isDirectory.boolValue
}
func convertToURL(path:String)-> URL?{
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].path
do {
_ = try FileManager.default.contentsOfDirectory(atPath: "\(docs)/\(path)")
return URL(fileURLWithPath: "\(docs)/\(path)", isDirectory: true)
} catch let error {
print("Error: \(error.localizedDescription)")
return nil
}
}
}

View File

@@ -0,0 +1,79 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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.
#if canImport(CoreGraphics)
import CoreGraphics
#endif
#if os(macOS) || os(iOS)
import Darwin
#elseif os(Linux)
import Glibc
#endif
public extension Float {
var int: Int {
return Int(self)
}
var double: Double {
return Double(self)
}
#if canImport(CoreGraphics)
var cgFloat: CGFloat {
return CGFloat(self)
}
#endif
var satsToBTC: Double {
return (self / bitcoinIntConvertion.float).double
}
var convertToBTC: Double {
return satsToBTC
}
var toBTC: Double {
return satsToBTC
}
/// Rounds the double to decimal places value
func rounded(toPlaces places:Int) -> Float {
let divisor = pow(10.0, Float(places))
return (self * divisor).rounded() / divisor
}
}
precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence }
infix operator **: PowerPrecedence
/// Value of exponentiation.
///
/// - Parameters:
/// - lhs: base float.
/// - rhs: exponent float.
/// - Returns: exponentiation result (4.4 ** 0.5 = 2.0976176963).
public func ** (lhs: Float, rhs: Float) -> Float {
// http://nshipster.com/swift-operators/
return pow(lhs, rhs)
}

View File

@@ -0,0 +1,254 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
#if canImport(Foundation)
import Foundation
#endif
#if canImport(CoreGraphics)
import CoreGraphics
#endif
#if os(macOS) || os(iOS)
import Darwin
#elseif os(Linux)
import Glibc
#endif
var bitcoinIntConvertion: Int {
return 100_000_000
}
// MARK: - Properties
public extension Int {
/// CountableRange 0..<Int.
var countableRange: CountableRange<Int> {
return 0..<self
}
/// Radian value of degree input.
var degreesToRadians: Double {
return Double.pi * Double(self) / 180.0
}
/// Degree value of radian input.
var radiansToDegrees: Double {
return Double(self) * 180 / Double.pi
}
/// UInt.
var uInt: UInt {
return UInt(self)
}
var uInt32: UInt32 {
return UInt32(truncatingIfNeeded: self)
}
var uInt64: UInt64 {
return UInt64(truncatingIfNeeded: self)
}
/// Double.
var double: Double {
return Double(self)
}
/// Float.
var float: Float {
return Float(self)
}
#if canImport(CoreGraphics)
/// CGFloat.
var cgFloat: CGFloat {
return CGFloat(self)
}
#endif
var satsToBTC: String {
let result = double / (bitcoinIntConvertion).double
return String(format: "%.8f", result)
}
var convertToBTC: String {
return satsToBTC
}
var toBTC: String {
return satsToBTC
}
var timestampToDate: Date {
return Date(timeIntervalSince1970: TimeInterval(self))
}
/// String formatted for values over ±1000 (example: 1k, -2k, 100k, 1kk, -5kk..).
var kFormatted: String {
var sign: String {
return self >= 0 ? "" : "-"
}
let abs = Swift.abs(self)
if abs == 0 {
return "0k"
} else if abs >= 0, abs < 1000 {
return "0k"
} else if abs >= 1000, abs < 1_000_000 {
return String(format: "\(sign)%ik", abs / 1000)
}
return String(format: "\(sign)%ikk", abs / 100_000)
}
/// Array of digits of integer value.
var digits: [Int] {
let abs = Swift.abs(self)
guard self != 0 else { return [0] }
var digits = [Int]()
var number = abs
while number != 0 {
let xNumber = number % 10
digits.append(xNumber)
number /= 10
}
digits.reverse()
return digits
}
/// Number of digits of integer value.
var digitsCount: Int {
let abs = Swift.abs(self)
guard self != 0 else { return 1 }
let number = Double(abs)
return Int(log10(number) + 1)
}
}
// MARK: - Methods
public extension Int {
/// check if given integer prime or not. Warning: Using big numbers can be computationally expensive!
/// - Returns: true or false depending on prime-ness.
func isPrime() -> Bool {
// To improve speed on latter loop :)
if self == 2 { return true }
guard self > 1, self % 2 != 0 else { return false }
// Explanation: It is enough to check numbers until
// the square root of that number. If you go up from N by one,
// other multiplier will go 1 down to get similar result
// (integer-wise operation) such way increases speed of operation
let base = Int(sqrt(Double(self)))
for int in Swift.stride(from: 3, through: base, by: 2) where self % int == 0 {
return false
}
return true
}
/// Roman numeral string from integer (if applicable).
///
/// 10.romanNumeral() -> "X"
///
/// - Returns: The roman numeral string.
func romanNumeral() -> String? {
// https://gist.github.com/kumo/a8e1cb1f4b7cff1548c7
guard self > 0 else { // there is no roman numeral for 0 or negative numbers
return nil
}
let romanValues = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
let arabicValues = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
var romanValue = ""
var startingValue = self
for (index, romanChar) in romanValues.enumerated() {
let arabicValue = arabicValues[index]
let div = startingValue / arabicValue
for _ in 0..<div {
romanValue.append(romanChar)
}
startingValue -= arabicValue * div
}
return romanValue
}
/// Rounds to the closest multiple of n.
func roundToNearest(_ number: Int) -> Int {
return number == 0 ? self : Int(round(Double(self) / Double(number))) * number
}
}
// MARK: - Operators
precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence }
infix operator **: PowerPrecedence
/// Value of exponentiation.
///
/// - Parameters:
/// - lhs: base integer.
/// - rhs: exponent integer.
/// - Returns: exponentiation result (example: 2 ** 3 = 8).
public func ** (lhs: Int, rhs: Int) -> Double {
// http://nshipster.com/swift-operators/
return pow(Double(lhs), Double(rhs))
}
// swiftlint:disable identifier_name
prefix operator
/// Square root of integer.
///
/// - Parameter int: integer value to find square root for.
/// - Returns: square root of given integer.
public prefix func (int: Int) -> Double {
// http://nshipster.com/swift-operators/
return sqrt(Double(int))
}
// swiftlint:enable identifier_name
// swiftlint:disable identifier_name
infix operator ±
/// Tuple of plus-minus operation.
///
/// - Parameters:
/// - lhs: integer number.
/// - rhs: integer number.
/// - Returns: tuple of plus-minus operation (example: 2 ± 3 -> (5, -1)).
public func ± (lhs: Int, rhs: Int) -> (Int, Int) {
// http://nshipster.com/swift-operators/
return (lhs + rhs, lhs - rhs)
}
// swiftlint:enable identifier_name
// swiftlint:disable identifier_name
prefix operator ±
/// Tuple of plus-minus operation.
///
/// - Parameter int: integer number.
/// - Returns: tuple of plus-minus operation (example: ± 2 -> (2, -2)).
public prefix func ± (int: Int) -> (Int, Int) {
// http://nshipster.com/swift-operators/
return (int, -int)
}

View File

@@ -0,0 +1,29 @@
//
// Copyright (c) 2024 Loverde Co.
//
// 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
public extension Sequence where Element == UInt8 {
var data: Data { .init(self) }
var base64Decoded: Data? { Data(base64Encoded: data) }
var string: String? { String(bytes: self, encoding: .utf8) }
}

View File

@@ -0,0 +1,45 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
public extension NSAttributedString {
//Example Usage
//
//let attributedString = NSAttributedString(html: ""<html><body> Some html string </body></html>"")
//myLabel.attributedText = attributedString
convenience init?(html: String) {
guard let data = html.data(using: String.Encoding.utf8, allowLossyConversion: false) else {
return nil
}
let attributedOptions: [NSAttributedString.DocumentReadingOptionKey : Any] = [
NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue): NSAttributedString.DocumentType.html,
NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue): String.Encoding.utf8.rawValue
]
guard let attributedString = try? NSMutableAttributedString(data: data, options: attributedOptions, documentAttributes: nil) else {
return nil
}
self.init(attributedString: attributedString)
}
}

View File

@@ -0,0 +1,41 @@
//
// Copyright (c) 2024 Loverde Co.
//
// 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
extension NSError {
public static func createErrorWith(code: Int, description: String, reasonForError: String) -> NSError {
let userInfo: [String : Any] =
[
NSLocalizedDescriptionKey : NSLocalizedString("Generic Error",
value: description,
comment: "") ,
NSLocalizedFailureReasonErrorKey : NSLocalizedString("Generic Error",
value: reasonForError,
comment: "")
]
return NSError(domain: LCEssentials.DEFAULT_ERROR_DOMAIN,
code: code,
userInfo: userInfo)
}
}

View File

@@ -0,0 +1,74 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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
import UIKit
extension NSLayoutConstraint {
func constraintWithMultiplier(_ multiplier: CGFloat) -> NSLayoutConstraint {
guard let first = firstItem else { return NSLayoutConstraint() }
return NSLayoutConstraint(item: first,
attribute: self.firstAttribute,
relatedBy: self.relation,
toItem: self.secondItem,
attribute: self.secondAttribute,
multiplier: multiplier,
constant: self.constant)
}
func matches(view: UIView, anchor: NSLayoutYAxisAnchor) -> Bool {
if let firstView = firstItem as? UIView,
firstView == view && firstAnchor == anchor {
return true
}
if let secondView = secondItem as? UIView,
secondView == view && secondAnchor == anchor {
return true
}
return false
}
func matches(view: UIView, anchor: NSLayoutXAxisAnchor) -> Bool {
if let firstView = firstItem as? UIView,
firstView == view && firstAnchor == anchor {
return true
}
if let secondView = secondItem as? UIView,
secondView == view && secondAnchor == anchor {
return true
}
return false
}
func matches(view: UIView, anchor: NSLayoutDimension) -> Bool {
if let firstView = firstItem as? UIView,
firstView == view && firstAnchor == anchor {
return true
}
if let secondView = secondItem as? UIView,
secondView == view && secondAnchor == anchor {
return true
}
return false
}
}

View File

@@ -0,0 +1,191 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
#if os(iOS) || os(macOS)
import UIKit
public extension NSMutableAttributedString {
@discardableResult func customize(_ text: String,
withFont font: UIFont,
color: UIColor? = nil,
lineSpace: CGFloat? = nil,
alignment: NSTextAlignment? = nil,
changeCurrentText: Bool = false) -> NSMutableAttributedString {
var attrs: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font]
if color != nil {
attrs[NSAttributedString.Key.foregroundColor] = color
}
if lineSpace != nil {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpace ?? 0
attrs[NSAttributedString.Key.paragraphStyle] = paragraphStyle
}
if let alignment = alignment {
let paragraph = NSMutableParagraphStyle()
paragraph.alignment = alignment
attrs[NSAttributedString.Key.paragraphStyle] = paragraph
}
if changeCurrentText {
self.addAttributes(attrs, range: self.mutableString.range(of: text))
} else {
let customStr = NSMutableAttributedString(string: "\(text)", attributes: attrs)
self.append(customStr)
}
return self
}
@discardableResult func underline(_ text: String,
withFont font: UIFont,
color: UIColor? = nil,
changeCurrentText: Bool = false) -> NSMutableAttributedString {
var attrs: [NSAttributedString.Key: Any] = [
NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue as AnyObject,
NSAttributedString.Key.font: font
]
if color != nil {
attrs[NSAttributedString.Key.foregroundColor] = color
}
if changeCurrentText {
self.addAttributes(attrs, range: self.mutableString.range(of: text))
} else {
let customStr = NSMutableAttributedString(string: "\(text)", attributes: attrs)
self.append(customStr)
}
return self
}
@discardableResult
func strikethrough(_ text: String, changeCurrentText: Bool = false) -> Self {
let attrs: [NSAttributedString.Key: Any] = [
.strikethroughStyle: NSUnderlineStyle.single.rawValue,
]
if changeCurrentText {
self.addAttributes(attrs, range: self.mutableString.range(of: text))
} else {
let customStr = NSMutableAttributedString(string: "\(text)", attributes: attrs)
self.append(customStr)
}
return self
}
@discardableResult func linkTouch(_ text: String,
url: String,
withFont font: UIFont,
color: UIColor = UIColor.blue,
changeCurrentText: Bool = false) -> NSMutableAttributedString {
let linkTerms: [NSAttributedString.Key: Any] = [
NSAttributedString.Key.link: NSURL(string: url) ?? NSURL(),
NSAttributedString.Key.foregroundColor: color,
NSAttributedString.Key.underlineColor: color,
NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,
NSAttributedString.Key.font: font
]
if changeCurrentText {
self.addAttributes(linkTerms, range: self.mutableString.range(of: text))
} else {
let customStr = NSMutableAttributedString(string: "\(text)", attributes: linkTerms)
self.append(customStr)
}
return self
}
@discardableResult func supperscript(_ text: String,
withFont font: UIFont,
color: UIColor? = nil,
offset: CGFloat,
changeCurrentText: Bool = false) -> NSMutableAttributedString {
var attrs: [NSAttributedString.Key: Any] = [
NSAttributedString.Key.baselineOffset: offset,
NSAttributedString.Key.font: font
]
if color != nil {
attrs[NSAttributedString.Key.foregroundColor] = color
}
if changeCurrentText {
self.addAttributes(attrs, range: self.mutableString.range(of: text))
} else {
let customStr = NSMutableAttributedString(string: "\(text)", attributes: attrs)
self.append(customStr)
}
return self
}
@discardableResult func appendImageToText(_ image: UIImage? = nil) -> NSMutableAttributedString {
let imageAttach = NSTextAttachment()
imageAttach.image = image
let imgStr = NSAttributedString(attachment: imageAttach)
append(imgStr)
return self
}
func attributtedString() -> NSAttributedString {
let range = self.string.range(of: self.string) ?? Range<String.Index>(uncheckedBounds: (self.string.startIndex, upper: self.string.endIndex))
let nsRange = self.string.nsRange(from: range) ?? NSRange()
return self.attributedSubstring(from: nsRange)
}
@discardableResult func normal(_ text: String) -> NSMutableAttributedString {
let normal = NSAttributedString(string: text)
self.append(normal)
return self
}
func canSetAsLink(textToFind: String, linkURL: String) -> Bool {
let foundRange = self.mutableString.range(of: textToFind)
if foundRange.location != NSNotFound {
self.addAttribute(NSAttributedString.Key.link, value: linkURL, range: foundRange)
return true
}
return false
}
func height(withConstrainedWidth width: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
return ceil(boundingBox.height)
}
func width(withConstrainedHeight height: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
return ceil(boundingBox.width)
}
}
#endif

View File

@@ -0,0 +1,45 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
public extension NSString {
var string: String? {
return String(describing: self)
}
func randomAlphaNumericString(_ length: Int = 8) -> String {
let allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let allowedCharsCount = UInt32(allowedChars.count)
var randomString = ""
for _ in (0..<length) {
let randomNum = Int(arc4random_uniform(allowedCharsCount))
let newCharacter = allowedChars[allowedChars.index(allowedChars.startIndex, offsetBy: randomNum)]
randomString += String(newCharacter)
}
return randomString
}
}

View File

@@ -0,0 +1,182 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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 extension Optional {
/// Get self of default value (if self is nil).
///
/// let foo: String? = nil
/// print(foo.unwrapped(or: "bar")) -> "bar"
///
/// let bar: String? = "bar"
/// print(bar.unwrapped(or: "foo")) -> "bar"
///
/// - Parameter defaultValue: default value to return if self is nil.
/// - Returns: self if not nil or default value if nil.
func unwrapped(or defaultValue: Wrapped) -> Wrapped {
// http://www.russbishop.net/improving-optionals
return self ?? defaultValue
}
/// Gets the wrapped value of an optional. If the optional is `nil`, throw a custom error.
///
/// let foo: String? = nil
/// try print(foo.unwrapped(or: MyError.notFound)) -> error: MyError.notFound
///
/// let bar: String? = "bar"
/// try print(bar.unwrapped(or: MyError.notFound)) -> "bar"
///
/// - Parameter error: The error to throw if the optional is `nil`.
/// - Returns: The value wrapped by the optional.
/// - Throws: The error passed in.
func unwrapped(or error: Error) throws -> Wrapped {
guard let wrapped = self else { throw error }
return wrapped
}
/// Runs a block to Wrapped if not nil
///
/// let foo: String? = nil
/// foo.run { unwrappedFoo in
/// // block will never run sice foo is nill
/// print(unwrappedFoo)
/// }
///
/// let bar: String? = "bar"
/// bar.run { unwrappedBar in
/// // block will run sice bar is not nill
/// print(unwrappedBar) -> "bar"
/// }
///
/// - Parameter block: a block to run if self is not nil.
func run(_ block: (Wrapped) -> Void) {
// http://www.russbishop.net/improving-optionals
_ = map(block)
}
/// Assign an optional value to a variable only if the value is not nil.
///
/// let someParameter: String? = nil
/// let parameters = [String: Any]() // Some parameters to be attached to a GET request
/// parameters[someKey] ??= someParameter // It won't be added to the parameters dict
///
/// - Parameters:
/// - lhs: Any?
/// - rhs: Any?
static func ??= (lhs: inout Optional, rhs: Optional) {
guard let rhs = rhs else { return }
lhs = rhs
}
/// Assign an optional value to a variable only if the variable is nil.
///
/// var someText: String? = nil
/// let newText = "Foo"
/// let defaultText = "Bar"
/// someText ?= newText // someText is now "Foo" because it was nil before
/// someText ?= defaultText // someText doesn't change its value because it's not nil
///
/// - Parameters:
/// - lhs: Any?
/// - rhs: Any?
static func ?= (lhs: inout Optional, rhs: @autoclosure () -> Optional) {
if lhs == nil {
lhs = rhs()
}
}
}
// MARK: - Methods (Collection)
public extension Optional where Wrapped: Collection {
/// Check if optional is nil or empty collection.
var isNilOrEmpty: Bool {
return self?.isEmpty ?? true
}
/// Returns the collection only if it is not nil and not empty.
var nonEmpty: Wrapped? {
return (self?.isEmpty ?? true) ? nil : self
}
}
// MARK: - Methods (RawRepresentable, RawValue: Equatable)
public extension Optional where Wrapped: RawRepresentable, Wrapped.RawValue: Equatable {
// swiftlint:disable missing_swifterswift_prefix
/// Returns a Boolean value indicating whether two values are equal.
///
/// Equality is the inverse of inequality. For any values `a` and `b`,
/// `a == b` implies that `a != b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
@inlinable static func == (lhs: Optional, rhs: Wrapped.RawValue?) -> Bool {
return lhs?.rawValue == rhs
}
/// Returns a Boolean value indicating whether two values are equal.
///
/// Equality is the inverse of inequality. For any values `a` and `b`,
/// `a == b` implies that `a != b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
@inlinable static func == (lhs: Wrapped.RawValue?, rhs: Optional) -> Bool {
return lhs == rhs?.rawValue
}
/// Returns a Boolean value indicating whether two values are not equal.
///
/// Inequality is the inverse of equality. For any values `a` and `b`,
/// `a != b` implies that `a == b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
@inlinable static func != (lhs: Optional, rhs: Wrapped.RawValue?) -> Bool {
return lhs?.rawValue != rhs
}
/// Returns a Boolean value indicating whether two values are not equal.
///
/// Inequality is the inverse of equality. For any values `a` and `b`,
/// `a != b` implies that `a == b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
@inlinable static func != (lhs: Wrapped.RawValue?, rhs: Optional) -> Bool {
return lhs != rhs?.rawValue
}
// swiftlint:enable missing_swifterswift_prefix
}
// MARK: - Operators
infix operator ??=: AssignmentPrecedence
infix operator ?=: AssignmentPrecedence

View File

@@ -0,0 +1,220 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
public extension RangeReplaceableCollection {
/// Creates a new collection of a given size where for each position of the collection the value will
/// be the result of a call of the given expression.
///
/// let values = Array(expression: "Value", count: 3)
/// print(values)
/// // Prints "["Value", "Value", "Value"]"
///
/// - Parameters:
/// - expression: The expression to execute for each position of the collection.
/// - count: The count of the collection.
init(expression: @autoclosure () throws -> Element, count: Int) rethrows {
self.init()
// swiftlint:disable:next empty_count
if count > 0 {
reserveCapacity(count)
while self.count < count {
try append(expression())
}
}
}
}
// MARK: - Methods
public extension RangeReplaceableCollection {
///  SwifterSwift: Returns a new rotated collection by the given places.
///
/// [1, 2, 3, 4].rotated(by: 1) -> [4,1,2,3]
/// [1, 2, 3, 4].rotated(by: 3) -> [2,3,4,1]
/// [1, 2, 3, 4].rotated(by: -1) -> [2,3,4,1]
///
/// - Parameter places: Number of places that the array be rotated. If the value is positive the end becomes the
/// start, if it negative it's that start become the end.
/// - Returns: The new rotated collection.
func rotated(by places: Int) -> Self {
// Inspired by: https://ruby-doc.org/core-2.2.0/Array.html#method-i-rotate
var copy = self
return copy.rotate(by: places)
}
///  SwifterSwift: Rotate the collection by the given places.
///
/// [1, 2, 3, 4].rotate(by: 1) -> [4,1,2,3]
/// [1, 2, 3, 4].rotate(by: 3) -> [2,3,4,1]
/// [1, 2, 3, 4].rotated(by: -1) -> [2,3,4,1]
///
/// - Parameter places: The number of places that the array should be rotated. If the value is positive the end
/// becomes the start, if it negative it's that start become the end.
/// - Returns: self after rotating.
@discardableResult
mutating func rotate(by places: Int) -> Self {
guard places != 0 else { return self }
let placesToMove = places % count
if placesToMove > 0 {
let range = index(endIndex, offsetBy: -placesToMove)...
let slice = self[range]
removeSubrange(range)
insert(contentsOf: slice, at: startIndex)
} else {
let range = startIndex..<index(startIndex, offsetBy: -placesToMove)
let slice = self[range]
removeSubrange(range)
append(contentsOf: slice)
}
return self
}
/// Removes the first element of the collection which satisfies the given predicate.
///
/// [1, 2, 2, 3, 4, 2, 5].removeFirst { $0 % 2 == 0 } -> [1, 2, 3, 4, 2, 5]
/// ["h", "e", "l", "l", "o"].removeFirst { $0 == "e" } -> ["h", "l", "l", "o"]
///
/// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that
/// indicates whether the passed element represents a match.
/// - Returns: The first element for which predicate returns true, after removing it. If no elements in the
/// collection satisfy the given predicate, returns `nil`.
@discardableResult
mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows -> Element? {
guard let index = try firstIndex(where: predicate) else { return nil }
return remove(at: index)
}
/// Remove a random value from the collection.
@discardableResult
mutating func removeRandomElement() -> Element? {
guard let randomIndex = indices.randomElement() else { return nil }
return remove(at: randomIndex)
}
/// Keep elements of Array while condition is true.
///
/// [0, 2, 4, 7].keep(while: { $0 % 2 == 0 }) -> [0, 2, 4]
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: self after applying provided condition.
/// - Throws: provided condition exception.
@discardableResult
mutating func keep(while condition: (Element) throws -> Bool) rethrows -> Self {
if let idx = try firstIndex(where: { try !condition($0) }) {
removeSubrange(idx...)
}
return self
}
/// Take element of Array while condition is true.
///
/// [0, 2, 4, 7, 6, 8].take( where: {$0 % 2 == 0}) -> [0, 2, 4]
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: All elements up until condition evaluates to false.
func take(while condition: (Element) throws -> Bool) rethrows -> Self {
return try Self(prefix(while: condition))
}
/// Skip elements of Array while condition is true.
///
/// [0, 2, 4, 7, 6, 8].skip( where: {$0 % 2 == 0}) -> [6, 8]
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: All elements after the condition evaluates to false.
func skip(while condition: (Element) throws -> Bool) rethrows -> Self {
guard let idx = try firstIndex(where: { try !condition($0) }) else { return Self() }
return Self(self[idx...])
}
/// Remove all duplicate elements using KeyPath to compare.
///
/// - Parameter path: Key path to compare, the value must be Equatable.
mutating func removeDuplicates<E: Equatable>(keyPath path: KeyPath<Element, E>) {
var items = [Element]()
removeAll { element -> Bool in
guard items.contains(where: { $0[keyPath: path] == element[keyPath: path] }) else {
items.append(element)
return false
}
return true
}
}
/// Remove all duplicate elements using KeyPath to compare.
///
/// - Parameter path: Key path to compare, the value must be Hashable.
mutating func removeDuplicates<E: Hashable>(keyPath path: KeyPath<Element, E>) {
var set = Set<E>()
removeAll { !set.insert($0[keyPath: path]).inserted }
}
/// Accesses the element at the specified position.
///
/// - Parameter offset: The offset position of the element to access. `offset` must be a valid index offset of the
/// collection that is not equal to the `endIndex` property.
subscript(offset: Int) -> Element {
get {
return self[index(startIndex, offsetBy: offset)]
}
set {
let offsetIndex = index(startIndex, offsetBy: offset)
replaceSubrange(offsetIndex..<index(after: offsetIndex), with: [newValue])
}
}
/// Accesses a contiguous subrange of the collections elements.
///
/// - Parameter range: A range of the collections indices offsets. The bounds of the range must be valid indices of
/// the collection.
subscript<R>(range: R) -> SubSequence where R: RangeExpression, R.Bound == Int {
get {
let indexRange = range.relative(to: 0..<count)
return self[index(startIndex, offsetBy: indexRange.lowerBound)..<index(startIndex,
offsetBy: indexRange.upperBound)]
}
set {
let indexRange = range.relative(to: 0..<count)
replaceSubrange(
index(startIndex, offsetBy: indexRange.lowerBound)..<index(startIndex, offsetBy: indexRange.upperBound),
with: newValue)
}
}
/**
Adds a new element at the end of the array, mutates the array in place
- Parameter newElement: The optional element to append to the array
*/
mutating func appendIfNonNil(_ newElement: Element?) {
guard let newElement = newElement else { return }
append(newElement)
}
/**
Adds the elements of a sequence to the end of the array, mutates the array in place
- Parameter newElements: The optional sequence to append to the array
*/
mutating func appendIfNonNil<S>(contentsOf newElements: S?) where Element == S.Element, S: Sequence {
guard let newElements = newElements else { return }
append(contentsOf: newElements)
}
}

View File

@@ -0,0 +1,330 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
public extension Sequence {
/// Check if all elements in collection match a condition.
///
/// [2, 2, 4].all(matching: {$0 % 2 == 0}) -> true
/// [1,2, 2, 4].all(matching: {$0 % 2 == 0}) -> false
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: true when all elements in the array match the specified condition.
func all(matching condition: (Element) throws -> Bool) rethrows -> Bool {
return try !contains { try !condition($0) }
}
/// Check if no elements in collection match a condition.
///
/// [2, 2, 4].none(matching: {$0 % 2 == 0}) -> false
/// [1, 3, 5, 7].none(matching: {$0 % 2 == 0}) -> true
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: true when no elements in the array match the specified condition.
func none(matching condition: (Element) throws -> Bool) rethrows -> Bool {
return try !contains { try condition($0) }
}
/// Check if any element in collection match a condition.
///
/// [2, 2, 4].any(matching: {$0 % 2 == 0}) -> false
/// [1, 3, 5, 7].any(matching: {$0 % 2 == 0}) -> true
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: true when no elements in the array match the specified condition.
func any(matching condition: (Element) throws -> Bool) rethrows -> Bool {
return try contains { try condition($0) }
}
/// Filter elements based on a rejection condition.
///
/// [2, 2, 4, 7].reject(where: {$0 % 2 == 0}) -> [7]
///
/// - Parameter condition: to evaluate the exclusion of an element from the array.
/// - Returns: the array with rejected values filtered from it.
func reject(where condition: (Element) throws -> Bool) rethrows -> [Element] {
return try filter { return try !condition($0) }
}
/// Get element count based on condition.
///
/// [2, 2, 4, 7].count(where: {$0 % 2 == 0}) -> 3
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: number of times the condition evaluated to true.
func count(where condition: (Element) throws -> Bool) rethrows -> Int {
var count = 0
for element in self where try condition(element) {
count += 1
}
return count
}
/// Iterate over a collection in reverse order. (right to left)
///
/// [0, 2, 4, 7].forEachReversed({ print($0)}) -> // Order of print: 7,4,2,0
///
/// - Parameter body: a closure that takes an element of the array as a parameter.
func forEachReversed(_ body: (Element) throws -> Void) rethrows {
try reversed().forEach(body)
}
/// Calls the given closure with each element where condition is true.
///
/// [0, 2, 4, 7].forEach(where: {$0 % 2 == 0}, body: { print($0)}) -> // print: 0, 2, 4
///
/// - Parameters:
/// - condition: condition to evaluate each element against.
/// - body: a closure that takes an element of the array as a parameter.
func forEach(where condition: (Element) throws -> Bool, body: (Element) throws -> Void) rethrows {
try lazy.filter(condition).forEach(body)
}
/// Reduces an array while returning each interim combination.
///
/// [1, 2, 3].accumulate(initial: 0, next: +) -> [1, 3, 6]
///
/// - Parameters:
/// - initial: initial value.
/// - next: closure that combines the accumulating value and next element of the array.
/// - Returns: an array of the final accumulated value and each interim combination.
func accumulate<U>(initial: U, next: (U, Element) throws -> U) rethrows -> [U] {
var runningTotal = initial
return try map { element in
runningTotal = try next(runningTotal, element)
return runningTotal
}
}
/// Filtered and map in a single operation.
///
/// [1,2,3,4,5].filtered({ $0 % 2 == 0 }, map: { $0.string }) -> ["2", "4"]
///
/// - Parameters:
/// - isIncluded: condition of inclusion to evaluate each element against.
/// - transform: transform element function to evaluate every element.
/// - Returns: Return an filtered and mapped array.
func filtered<T>(_ isIncluded: (Element) throws -> Bool, map transform: (Element) throws -> T) rethrows -> [T] {
return try lazy.filter(isIncluded).map(transform)
}
/// Get the only element based on a condition.
///
/// [].single(where: {_ in true}) -> nil
/// [4].single(where: {_ in true}) -> 4
/// [1, 4, 7].single(where: {$0 % 2 == 0}) -> 4
/// [2, 2, 4, 7].single(where: {$0 % 2 == 0}) -> nil
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: The only element in the array matching the specified condition. If there are more matching elements,
/// nil is returned. (optional)
func single(where condition: (Element) throws -> Bool) rethrows -> Element? {
var singleElement: Element?
for element in self where try condition(element) {
guard singleElement == nil else {
singleElement = nil
break
}
singleElement = element
}
return singleElement
}
/// Remove duplicate elements based on condition.
///
/// [1, 2, 1, 3, 2].withoutDuplicates { $0 } -> [1, 2, 3]
/// [(1, 4), (2, 2), (1, 3), (3, 2), (2, 1)].withoutDuplicates { $0.0 } -> [(1, 4), (2, 2), (3, 2)]
///
/// - Parameter transform: A closure that should return the value to be evaluated for repeating elements.
/// - Returns: Sequence without repeating elements
/// - Complexity: O(*n*), where *n* is the length of the sequence.
func withoutDuplicates<T: Hashable>(transform: (Element) throws -> T) rethrows -> [Element] {
var set = Set<T>()
return try filter { try set.insert(transform($0)).inserted }
}
///  SwifterSwift: Separates all items into 2 lists based on a given predicate. The first list contains all items
/// for which the specified condition evaluates to true. The second list contains those that don't.
///
/// let (even, odd) = [0, 1, 2, 3, 4, 5].divided { $0 % 2 == 0 }
/// let (minors, adults) = people.divided { $0.age < 18 }
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: A tuple of matched and non-matched items
func divided(by condition: (Element) throws -> Bool) rethrows -> (matching: [Element], nonMatching: [Element]) {
// Inspired by: http://ruby-doc.org/core-2.5.0/Enumerable.html#method-i-partition
var matching = [Element]()
var nonMatching = [Element]()
for element in self {
// swiftlint:disable:next void_function_in_ternary
try condition(element) ? matching.append(element) : nonMatching.append(element)
}
return (matching, nonMatching)
}
/// Return a sorted array based on a key path and a compare function.
///
/// - Parameter keyPath: Key path to sort by.
/// - Parameter compare: Comparison function that will determine the ordering.
/// - Returns: The sorted array.
func sorted<T>(by keyPath: KeyPath<Element, T>, with compare: (T, T) -> Bool) -> [Element] {
return sorted { compare($0[keyPath: keyPath], $1[keyPath: keyPath]) }
}
/// Return a sorted array based on a key path.
///
/// - Parameter keyPath: Key path to sort by. The key path type must be Comparable.
/// - Returns: The sorted array.
func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {
return sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
}
/// Returns a sorted sequence based on two key paths. The second one will be used in case the values
/// of the first one match.
///
/// - Parameters:
/// - keyPath1: Key path to sort by. Must be Comparable.
/// - keyPath2: Key path to sort by in case the values of `keyPath1` match. Must be Comparable.
func sorted<T: Comparable, U: Comparable>(by keyPath1: KeyPath<Element, T>,
and keyPath2: KeyPath<Element, U>) -> [Element] {
return sorted {
if $0[keyPath: keyPath1] != $1[keyPath: keyPath1] {
return $0[keyPath: keyPath1] < $1[keyPath: keyPath1]
}
return $0[keyPath: keyPath2] < $1[keyPath: keyPath2]
}
}
/// Returns a sorted sequence based on three key paths. Whenever the values of one key path match, the
/// next one will be used.
///
/// - Parameters:
/// - keyPath1: Key path to sort by. Must be Comparable.
/// - keyPath2: Key path to sort by in case the values of `keyPath1` match. Must be Comparable.
/// - keyPath3: Key path to sort by in case the values of `keyPath1` and `keyPath2` match. Must be Comparable.
func sorted<T: Comparable, U: Comparable, V: Comparable>(by keyPath1: KeyPath<Element, T>,
and keyPath2: KeyPath<Element, U>,
and keyPath3: KeyPath<Element, V>) -> [Element] {
return sorted {
if $0[keyPath: keyPath1] != $1[keyPath: keyPath1] {
return $0[keyPath: keyPath1] < $1[keyPath: keyPath1]
}
if $0[keyPath: keyPath2] != $1[keyPath: keyPath2] {
return $0[keyPath: keyPath2] < $1[keyPath: keyPath2]
}
return $0[keyPath: keyPath3] < $1[keyPath: keyPath3]
}
}
/// Sum of a `AdditiveArithmetic` property of each `Element` in a `Sequence`.
///
/// ["James", "Wade", "Bryant"].sum(for: \.count) -> 15
///
/// - Parameter keyPath: Key path of the `AdditiveArithmetic` property.
/// - Returns: The sum of the `AdditiveArithmetic` properties at `keyPath`.
func sum<T: AdditiveArithmetic>(for keyPath: KeyPath<Element, T>) -> T {
// Inspired by: https://swiftbysundell.com/articles/reducers-in-swift/
return reduce(.zero) { $0 + $1[keyPath: keyPath] }
}
/// Returns the first element of the sequence with having property by given key path equals to given
/// `value`.
///
/// - Parameters:
/// - keyPath: The `KeyPath` of property for `Element` to compare.
/// - value: The value to compare with `Element` property.
/// - Returns: The first element of the collection that has property by given key path equals to given `value` or
/// `nil` if there is no such element.
func first<T: Equatable>(where keyPath: KeyPath<Element, T>, equals value: T) -> Element? {
return first { $0[keyPath: keyPath] == value }
}
}
public extension Sequence where Element: Equatable {
/// Check if array contains an array of elements.
///
/// [1, 2, 3, 4, 5].contains([1, 2]) -> true
/// [1.2, 2.3, 4.5, 3.4, 4.5].contains([2, 6]) -> false
/// ["h", "e", "l", "l", "o"].contains(["l", "o"]) -> true
///
/// - Parameter elements: array of elements to check.
/// - Returns: true if array contains all given items.
/// - Complexity: _O(m·n)_, where _m_ is the length of `elements` and _n_ is the length of this sequence.
func contains(_ elements: [Element]) -> Bool {
return elements.allSatisfy { contains($0) }
}
}
public extension Sequence where Element: Hashable {
/// Check if array contains an array of elements.
///
/// [1, 2, 3, 4, 5].contains([1, 2]) -> true
/// [1.2, 2.3, 4.5, 3.4, 4.5].contains([2, 6]) -> false
/// ["h", "e", "l", "l", "o"].contains(["l", "o"]) -> true
///
/// - Parameter elements: array of elements to check.
/// - Returns: true if array contains all given items.
/// - Complexity: _O(m + n)_, where _m_ is the length of `elements` and _n_ is the length of this sequence.
func contains(_ elements: [Element]) -> Bool {
let set = Set(self)
return elements.allSatisfy { set.contains($0) }
}
/// Check whether a sequence contains duplicates.
///
/// - Returns: true if the receiver contains duplicates.
func containsDuplicates() -> Bool {
var set = Set<Element>()
return contains { !set.insert($0).inserted }
}
/// Getting the duplicated elements in a sequence.
///
/// [1, 1, 2, 2, 3, 3, 3, 4, 5].duplicates().sorted() -> [1, 2, 3])
/// ["h", "e", "l", "l", "o"].duplicates().sorted() -> ["l"])
///
/// - Returns: An array of duplicated elements.
///
func duplicates() -> [Element] {
var set = Set<Element>()
var duplicates = Set<Element>()
forEach {
if !set.insert($0).inserted {
duplicates.insert($0)
}
}
return Array(duplicates)
}
}
// MARK: - Methods (AdditiveArithmetic)
public extension Sequence where Element: AdditiveArithmetic {
/// Sum of all elements in array.
///
/// [1, 2, 3, 4, 5].sum() -> 15
///
/// - Returns: sum of the array's elements.
func sum() -> Element {
return reduce(.zero, +)
}
}

View File

@@ -0,0 +1,75 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
#if canImport(Foundation)
import Foundation
#endif
// MARK: - Properties
public extension SignedNumeric {
/// String.
var string: String {
return String(describing: self)
}
#if canImport(Foundation)
/// String with number and current locale currency.
var asLocaleCurrency: String? {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale.current
// swiftlint:disable:next force_cast
return formatter.string(from: self as! NSNumber)
}
func asCurrency(locale: Locale = Locale.init(identifier: "pt_BR")) -> String? {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = locale
// swiftlint:disable:next force_cast
return formatter.string(from: self as! NSNumber)
}
#endif
}
// MARK: - Methods
public extension SignedNumeric {
#if canImport(Foundation)
/// Spelled out representation of a number.
///
/// print((12.32).spelledOutString()) // prints "twelve point three two"
///
/// - Parameter locale: Locale, default is .current.
/// - Returns: String representation of number spelled in specified locale language. E.g. input 92, output in "en":
/// "ninety-two".
func spelledOutString(locale: Locale = .current) -> String? {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.numberStyle = .spellOut
guard let number = self as? NSNumber else { return nil }
return formatter.string(from: number)
}
#endif
}

View File

@@ -0,0 +1,977 @@
//
// Copyright (c) 2018 SwifterSwift.
//
// 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
import UIKit
#if canImport(CommonCrypto)
import CommonCrypto
#endif
public extension String {
// MARK: - Variables
private var convertHtmlToNSAttributedString: NSAttributedString? {
guard let data = data(using: .utf8) else {
return nil
}
do {
return try NSAttributedString(data: data,
options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
], documentAttributes: nil)
} catch {
print(error.localizedDescription)
return nil
}
}
var convertToHTML: NSAttributedString? {
return convertHtmlToAttributedStringWithCSS(font: nil,
csscolor: "",
lineheight: 0,
csstextalign: "")
}
/// Check if string is a valid URL.
///
/// "https://google.com".isValidUrl -> true
///
var isValidUrl: Bool {
return URL(string: self) != nil
}
#if canImport(Foundation)
/// Check if string is a valid https URL.
///
/// "https://google.com".isValidHttpsUrl -> true
///
var isValidHttpsUrl: Bool {
guard let url = URL(string: self) else { return false }
return url.scheme == "https"
}
#endif
#if canImport(Foundation)
/// Check if string is a valid http URL.
///
/// "http://google.com".isValidHttpUrl -> true
///
var isValidHttpUrl: Bool {
guard let url = URL(string: self) else { return false }
return url.scheme == "http"
}
#endif
/// Readable string from a URL string.
///
/// "it's%20easy%20to%20decode%20strings".urlDecoded -> "it's easy to decode strings"
///
var urlDecoded: String {
return removingPercentEncoding ?? self
}
/// SwifterSwift.: URL escaped string.
///
/// "it's easy to encode strings".urlEncoded -> "it's%20easy%20to%20encode%20strings"
///
var urlEncoded: String {
return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
}
/// String without spaces and new lines.
///
/// " \n Swifter \n Swift ".withoutSpacesAndNewLines -> "SwifterSwift"
///
var withoutSpacesAndNewLines: String {
return replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "\n", with: "")
}
var isEmail: Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,20}"
let emailTest = NSPredicate(format: "SELF MATCHES %@", emailRegEx)
return emailTest.evaluate(with: self)
}
var isCPF: Bool {
let cpf = self.onlyNumbers
guard cpf.count == 11 else { return false }
let i1 = cpf.index(cpf.startIndex, offsetBy: 9)
let i2 = cpf.index(cpf.startIndex, offsetBy: 10)
let i3 = cpf.index(cpf.startIndex, offsetBy: 11)
let d1 = Int(cpf[i1..<i2])
let d2 = Int(cpf[i2..<i3])
var temp1 = 0, temp2 = 0
for i in 0...8 {
let start = cpf.index(cpf.startIndex, offsetBy: i)
let end = cpf.index(cpf.startIndex, offsetBy: i+1)
let char = Int(cpf[start..<end])
temp1 += char! * (10 - i)
temp2 += char! * (11 - i)
}
temp1 %= 11
temp1 = temp1 < 2 ? 0 : 11-temp1
temp2 += temp1 * 2
temp2 %= 11
temp2 = temp2 < 2 ? 0 : 11-temp2
return temp1 == d1 && temp2 == d2
}
var bool: Bool? {
let selfLowercased = trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
switch selfLowercased {
case "true", "yes", "1":
return true
case "false", "no", "0":
return false
default:
return nil
}
}
var JSONStringToDictionary: [String:Any]? {
if let data = self.data(using: .utf8) {
do {
let jsonString = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
return jsonString
} catch {
printError(title: "JSON STRING", msg: error.localizedDescription)
return nil
}
}else{
return nil
}
}
var currencyStringToDouble: Double {
let formatter = NumberFormatter()
let str = self.replacingOccurrences(of: " ", with: "")
formatter.numberStyle = .currency
formatter.locale = Locale(identifier: "pt_br")
let number = formatter.number(from: str)
return number?.doubleValue ?? 0.0
}
var removeSpecialChars: String {
let okayChars = Set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLKMNOPQRSTUVWXYZ1234567890-")
return self.filter {okayChars.contains($0) }
}
var removeHTMLTags: String {
return self.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression, range: nil)
}
var removeEmoji: String {
return self.components(separatedBy: CharacterSet.symbols).joined()
}
var alphanumeric: String {
return self.components(separatedBy: CharacterSet.alphanumerics.inverted).joined()
}
/// Check if string contains only letters.
///
/// "abc".isAlphabetic -> true
/// "123abc".isAlphabetic -> false
///
var isAlphabetic: Bool {
let hasLetters = rangeOfCharacter(from: .letters, options: .numeric, range: nil) != nil
let hasNumbers = rangeOfCharacter(from: .decimalDigits, options: .literal, range: nil) != nil
return hasLetters && !hasNumbers
}
/// Check if string contains at least one letter and one number.
///
/// // useful for passwords
/// "123abc".isAlphaNumeric -> true
/// "abc".isAlphaNumeric -> false
///
var isAlphaNumeric: Bool {
let hasLetters = rangeOfCharacter(from: .letters, options: .numeric, range: nil) != nil
let hasNumbers = rangeOfCharacter(from: .decimalDigits, options: .literal, range: nil) != nil
let comps = components(separatedBy: .alphanumerics)
return comps.joined(separator: "").count == 0 && hasLetters && hasNumbers
}
var alphanumericWithWhiteSpace: String {
return self.components(separatedBy: CharacterSet.alphanumerics.union(.whitespaces).inverted).joined()
}
var lettersWithWhiteSpace: String {
return self.components(separatedBy: CharacterSet.letters.union(.whitespaces).inverted).joined()
}
var letters: String {
return self.components(separatedBy: CharacterSet.letters.inverted).joined()
}
var numbers: String {
return components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
}
var data: Data {
return Data(self.utf8)
}
/// Retorna a URL encontrada na string, caso exista. Se não, retorna nulo
///
/// let string = "Meu www.algumsite.com.br tem tudo que voce precisa."
/// print(string.url)
/// //Optional(www.algumsite.com.br)
var url: String? {
do {
let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count))
for match in matches {
return (self as NSString).substring(with: match.range)
}
} catch {
return nil
}
return nil
}
/// Retorna true caso contenha tag HTML na String
///
/// let string = "Meu <a href="www.algumsite.com.br">site</a> tem tudo que voce precisa."
/// print(string.isHTML)
/// //true
var isHTML: Bool {
return self.range(of: "<[^>]+>", options: .regularExpression, range: nil, locale: nil) != nil
}
var onlyNumbers: String {
guard !isEmpty else { return "" }
return replacingOccurrences(of: "\\D",
with: "",
options: .regularExpression,
range: startIndex..<endIndex)
}
var isValidCNPJ: Bool {
let numbers = compactMap({ $0.wholeNumberValue })
guard numbers.count == 14 && Set(numbers).count != 1 else { return false }
func digitCalculator(_ slice: ArraySlice<Int>) -> Int {
var number = 1
let digit = 11 - slice.reversed().reduce(into: 0) {
number += 1
$0 += $1 * number
if number == 9 { number = 1 }
} % 11
return digit % 10
}
let dv1 = digitCalculator(numbers.prefix(12))
let dv2 = digitCalculator(numbers.prefix(13))
return dv1 == numbers[12] && dv2 == numbers[13]
}
var currentTimeZone : String {
let date = NSDate();
let formatter = DateFormatter();
formatter.dateFormat = "ZZZ";
let defaultTimeZoneStr = formatter.string(from: date as Date);
return defaultTimeZoneStr;
}
var first: String {
return String(prefix(1))
}
var last: String {
return String(suffix(1))
}
var uppercaseFirst: String {
return first.uppercased() + String(dropFirst())
}
var toURL: NSURL? {
return NSURL(string: self)
}
/// Integer value from string (if applicable).
///
/// "101".int -> 101
///
var int: Int? {
return Int(self)
}
var float: Float? {
return Float(self)
}
var double: Double? {
return Double(self)
}
var btcToSats: Int {
let decamal = Decimal(string: self) ?? Decimal()
var scaledResult = decamal * Decimal(bitcoinIntConvertion)
return NSDecimalNumber(decimal: scaledResult).intValue
}
var bitcoinToSatoshis: Int {
btcToSats
}
/// NSString from a string.
var nsString: NSString {
return NSString(string: self)
}
/// The full `NSRange` of the string.
var fullNSRange: NSRange { NSRange(startIndex..<endIndex, in: self) }
var base64Encode: String? {
return data(using: .utf8)?.base64EncodedString()
}
var base64Decode: String? {
guard let data = Data(base64Encoded: self) else { return nil }
return String(data: data, encoding: .utf8)
}
// MARK: - Methods
/// Loverde Co.: Replace Dictionaryes parameters to URL String.
@discardableResult
func replaceURL(_ withDict: [String: Any]) -> String {
var strOutput = self
for (key, Value) in withDict {
strOutput = strOutput.replacingOccurrences(of: "{\(key)}", with: "\(Value)")
}
return strOutput
}
/// Lorem ipsum string of given length.
///
/// - Parameter length: number of characters to limit lorem ipsum to (default is 445 - full lorem ipsum).
/// - Returns: Lorem ipsum dolor sit amet... string.
static func loremIpsum(ofLength length: Int = 445) -> String {
guard length > 0 else { return "" }
// https://www.lipsum.com/
let loremIpsum = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
"""
if loremIpsum.count > length {
return String(loremIpsum[loremIpsum.startIndex..<loremIpsum.index(loremIpsum.startIndex, offsetBy: length)])
}
return loremIpsum
}
#if canImport(Foundation)
/// An array of all words in a string.
///
/// "Swift is amazing".words() -> ["Swift", "is", "amazing"]
///
/// - Returns: The words contained in a string.
func words() -> [String] {
// https://stackoverflow.com/questions/42822838
let characterSet = CharacterSet.whitespacesAndNewlines.union(.punctuationCharacters)
let comps = components(separatedBy: characterSet)
return comps.filter { !$0.isEmpty }
}
/// Count of words in a string.
///
/// "Swift is amazing".wordsCount() -> 3
///
/// - Returns: The count of words contained in a string.
func wordCount() -> Int {
// https://stackoverflow.com/questions/42822838
let characterSet = CharacterSet.whitespacesAndNewlines.union(.punctuationCharacters)
let comps = components(separatedBy: characterSet)
let words = comps.filter { !$0.isEmpty }
return words.count
}
/// Check if string contains one or more instance of substring.
///
/// "Hello World!".contain("O") -> false
/// "Hello World!".contain("o", caseSensitive: false) -> true
///
/// - Parameters:
/// - string: substring to search for.
/// - caseSensitive: set true for case sensitive search (default is true).
/// - Returns: true if string contains one or more instance of substring.
func contains(_ string: String, caseSensitive: Bool = true) -> Bool {
if !caseSensitive {
return range(of: string, options: .caseInsensitive) != nil
}
return range(of: string) != nil
}
#endif
/// Reverse string.
@discardableResult
mutating func reverse() -> String {
let chars: [Character] = reversed()
self = String(chars)
return self
}
/// Returns a string by padding to fit the length parameter size with another string in the start.
///
/// "hue".paddingStart(10) -> " hue"
/// "hue".paddingStart(10, with: "br") -> "brbrbrbhue"
///
/// - Parameter length: The target length to pad.
/// - Parameter string: Pad string. Default is " ".
/// - Returns: The string with the padding on the start.
func paddingStart(_ length: Int, with string: String = " ") -> String {
guard count < length else { return self }
let padLength = length - count
if padLength < string.count {
return string[string.startIndex..<string.index(string.startIndex, offsetBy: padLength)] + self
} else {
var padding = string
while padding.count < padLength {
padding.append(string)
}
return padding[padding.startIndex..<padding.index(padding.startIndex, offsetBy: padLength)] + self
}
}
/// Returns a string by padding to fit the length parameter size with another string in the end.
///
/// "hue".paddingEnd(10) -> "hue "
/// "hue".paddingEnd(10, with: "br") -> "huebrbrbrb"
///
/// - Parameter length: The target length to pad.
/// - Parameter string: Pad string. Default is " ".
/// - Returns: The string with the padding on the end.
func paddingEnd(_ length: Int, with string: String = " ") -> String {
guard count < length else { return self }
let padLength = length - count
if padLength < string.count {
return self + string[string.startIndex..<string.index(string.startIndex, offsetBy: padLength)]
} else {
var padding = string
while padding.count < padLength {
padding.append(string)
}
return self + padding[padding.startIndex..<padding.index(padding.startIndex, offsetBy: padLength)]
}
}
func nsRange(from range: Range<String.Index>) -> NSRange? {
let utf16view = self.utf16
if let from = range.lowerBound.samePosition(in: utf16view), let to = range.upperBound.samePosition(in: utf16view) {
return NSMakeRange(utf16view.distance(from: utf16view.startIndex, to: from), utf16view.distance(from: from, to: to))
}
return nil
}
mutating func insertAtIndexEnd(string: String, ind: Int) {
self.insert(contentsOf: string, at: self.index(self.endIndex, offsetBy: ind) )
}
mutating func insertAtIndexStart(string: String, ind: Int) {
self.insert(contentsOf: string, at: self.index(self.endIndex, offsetBy: ind) )
}
/// Convert URL string to readable string.
///
/// var str = "it's%20easy%20to%20decode%20strings"
/// str.urlDecode()
/// print(str) // prints "it's easy to decode strings"
///
@discardableResult
mutating func urlDecode() -> String {
if let decoded = removingPercentEncoding {
self = decoded
}
return self
}
/// Escape string.
///
/// var str = "it's easy to encode strings"
/// str.urlEncode()
/// print(str) // prints "it's%20easy%20to%20encode%20strings"
///
@discardableResult
mutating func urlEncode() -> String {
if let encoded = addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) {
self = encoded
}
return self
}
/// Verifica se o texto equivale a verdadeiro ou falso.
func validateBolean(comparingBoolean:Bool = true) ->Bool{
if(comparingBoolean)
{
if (self.uppercased() == "TRUE") {return true}
if (self.uppercased() == "YES") {return true}
if (self.uppercased() == "ON") {return true}
if (self.uppercased() == "ONLINE") {return true}
if (self.uppercased() == "ENABLE") {return true}
if (self.uppercased() == "ACTIVATED") {return true}
if (self.uppercased() == "ONE") {return true}
//
if (self.uppercased() == "VERDADEIRO") {return true}
if (self.uppercased() == "SIM") {return true}
if (self.uppercased() == "LIGADO") {return true}
if (self.uppercased() == "ATIVO") {return true}
if (self.uppercased() == "ATIVADO") {return true}
if (self.uppercased() == "HABILITADO") {return true}
if (self.uppercased() == "UM") {return true}
//
if (self.uppercased() == "1") {return true}
if (self.uppercased() == "T") {return true}
if (self.uppercased() == "Y") {return true}
if (self.uppercased() == "S") {return true}
}
else
{
if (self.uppercased() == "FALSE") {return true}
if (self.uppercased() == "NO") {return true}
if (self.uppercased() == "OFF") {return true}
if (self.uppercased() == "OFFLINE") {return true}
if (self.uppercased() == "DISABLED") {return true}
if (self.uppercased() == "DEACTIVATED") {return true}
if (self.uppercased() == "ZERO") {return true}
//
if (self.uppercased() == "FALSO") {return true}
if (self.uppercased() == "NÃO") {return true}
if (self.uppercased() == "NAO") {return true}
if (self.uppercased() == "DESLIGADO") {return true}
if (self.uppercased() == "DESATIVADO") {return true}
if (self.uppercased() == "DESABILITADO") {return true}
//
if (self.uppercased() == "0") {return true}
if (self.uppercased() == "F") {return true}
if (self.uppercased() == "N") {return true}
}
return false;
}
func randomString(length: Int) -> String {
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let len = UInt32(letters.length)
var randomString = ""
for _ in 0 ..< length {
let rand = arc4random_uniform(len)
var nextChar = letters.character(at: Int(rand))
randomString += NSString(characters: &nextChar, length: 1) as String
}
return randomString
}
func stringFromTimeInterval(_ interval:TimeInterval) -> NSString {
let ti = NSInteger(interval)
let ms = Int((interval.truncatingRemainder(dividingBy: 1)) * 1000)
let seconds = ti % 60
let minutes = (ti / 60) % 60
let hours = (ti / 3600)
return NSString(format: "%0.2d:%0.2d:%0.2d.%0.3d",hours,minutes,seconds,ms)
}
/// LoverdeCo: String to Date object.
///
/// - Parameters:
/// - withCurrFormatt: Give a input formatt as it comes in String.
/// - Returns: Date object.
func date(withCurrFormatt: String = "yyyy-MM-dd HH:mm:ss",
localeIdentifier: String = "pt-BR",
timeZone: TimeZone? = TimeZone.current) -> Date? {
let updatedString:String = self.replacingOccurrences(of: " 0000", with: " +0000")
let dateFormatter:DateFormatter = DateFormatter.init()
let calendar:Calendar = Calendar.init(identifier: Calendar.Identifier.gregorian)
let enUSPOSIXLocale:Locale = Locale.init(identifier: localeIdentifier)
//
dateFormatter.timeZone = timeZone
//
dateFormatter.calendar = calendar
dateFormatter.locale = enUSPOSIXLocale
dateFormatter.dateFormat = withCurrFormatt
//
return dateFormatter.date(from: updatedString)
}
/// LoverdeCo: String to Date object.
///
/// - Parameters:
/// - withCurrFormatt: Give a input formatt as it comes in String.
/// - newFormatt: Give a new formatt you want in String
/// - Returns:
/// Date object.
func date(withCurrFormatt: String = "yyyy-MM-dd HH:mm:ss",
newFormatt: String = "yyyy-MM-dd HH:mm:ss",
localeIdentifier: String = "pt-BR",
timeZone: TimeZone? = TimeZone.current) -> Date? {
let date = self.date(withCurrFormatt: withCurrFormatt, localeIdentifier: localeIdentifier, timeZone: timeZone)
let strDate = date?.string(withFormat: newFormatt)
return strDate?.date(withCurrFormatt: newFormatt, localeIdentifier: localeIdentifier, timeZone: timeZone)
}
#if os(iOS) || os(macOS)
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.height)
}
func width(withConstraintedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.width)
}
#endif
func exponentize(str: String) -> String {
let supers = [
"1": "\u{00B9}",
"2": "\u{00B2}",
"3": "\u{00B3}",
"4": "\u{2074}",
"5": "\u{2075}",
"6": "\u{2076}",
"7": "\u{2077}",
"8": "\u{2078}",
"9": "\u{2079}"]
var newStr = ""
var isExp = false
for (_, char) in str.enumerated() {
if char == "^" {
isExp = true
} else {
if isExp {
let key = String(char)
if supers.keys.contains(key) {
newStr.append(Character(supers[key]!))
} else {
isExp = false
newStr.append(char)
}
} else {
newStr.append(char)
}
}
}
return newStr
}
/// Truncate string (cut it to a given number of characters).
///
/// var str = "This is a very long sentence"
/// str.truncate(toLength: 14)
/// print(str) // prints "This is a very..."
///
/// - Parameters:
/// - toLength: maximum number of characters before cutting.
/// - trailing: string to add at the end of truncated string (default is "...").
@discardableResult
mutating func truncate(toLength length: Int, trailing: String? = "...") -> String {
guard length > 0 else { return self }
if count > length {
self = self[startIndex..<index(startIndex, offsetBy: length)] + (trailing ?? "")
}
return self
}
/// Truncated string (limited to a given number of characters).
///
/// "This is a very long sentence".truncated(toLength: 14) -> "This is a very..."
/// "Short sentence".truncated(toLength: 14) -> "Short sentence"
///
/// - Parameters:
/// - toLength: maximum number of characters before cutting.
/// - trailing: string to add at the end of truncated string.
/// - Returns: truncated string (this is an extr...).
func truncated(toLength length: Int, trailing: String? = "...") -> String {
guard 1..<count ~= length else { return self }
return self[startIndex..<index(startIndex, offsetBy: length)] + (trailing ?? "")
}
func replacing(range: CountableClosedRange<Int>, with replacementString: String) -> String {
let start = index(startIndex, offsetBy: range.lowerBound)
let end = index(start, offsetBy: range.count)
return self.replacingCharacters(in: start ..< end, with: replacementString)
}
func findAndReplace<Target, Replacement>(from this: Target, to that: Replacement) -> String
where Target : StringProtocol, Replacement : StringProtocol {
return self.replacingOccurrences(of: this, with: that)
}
func replace(from: String, to: String) -> String {
return self.findAndReplace(from: from, to: to)
}
func replacingLastOccurrenceOfString(_ searchString: String, with replacementString: String, caseInsensitive: Bool = true) -> String {
let options: String.CompareOptions
if caseInsensitive {
options = [.backwards, .caseInsensitive]
} else {
options = [.backwards]
}
if let range = self.range(of: searchString, options: options, range: nil, locale: nil) {
return self.replacingCharacters(in: range, with: replacementString)
}
return self
}
/// Converte String para HTML com CSS.
///
/// - Parameters:
/// - font: Caso necessite uma fonte diferente ou que seja considerado o CSS nessa conversão.
/// - csscolor: Cor da font em formato ASCII.
/// - lineheight: Quantas linhas o texto pode conter. Digite 0 (zero) caso queira dinâmico.
/// - csstextalign: Alinhamento do texto em formato CSS.
/// - customCSS: Adiciona CSS customizado (Opcional).
/// - Returns:
/// Retorna um NSAttributedString convertido.
func convertHtmlToAttributedStringWithCSS(font: UIFont?,
csscolor: String,
lineheight: Int,
csstextalign: String,
customCSS: String? = nil) -> NSAttributedString? {
guard let font = font else { return convertHtmlToNSAttributedString }
let modifiedString = """
<style>
body{ font-family: '\(font.fontName)';
font-size:\(font.pointSize)px;
color: \(csscolor);
line-height: \(lineheight)px;
text-align: \(csstextalign); }
\(customCSS ?? "")
</style>\(self)
""";
guard let data = modifiedString.data(using: .utf8) else {
return nil
}
do {
return try NSAttributedString(data: data,
options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
], documentAttributes: nil)
} catch {
print(error.localizedDescription)
return nil
}
}
/// Float value from string (if applicable).
///
/// - Parameter locale: Locale (default is Locale.current)
/// - Returns: Optional Float value from given string.
func float(locale: Locale = .current) -> Float? {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.allowsFloats = true
return formatter.number(from: self)?.floatValue
}
/// Double value from string (if applicable).
///
/// - Parameter locale: Locale (default is Locale.current)
/// - Returns: Optional Double value from given string.
func double(locale: Locale = .current) -> Double? {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.allowsFloats = true
return formatter.number(from: self)?.doubleValue
}
/// Returns a localized string, with an optional comment for translators.
///
/// "Hello world".localized -> Hallo Welt
///
func localized(comment: String = "") -> String {
return NSLocalizedString(self, comment: comment)
}
/// First character of string (if applicable).
///
/// "Hello".firstCharacterAsString -> Optional("H")
/// "".firstCharacterAsString -> nil
///
var firstCharacterAsString: String? {
guard let first = first else { return nil }
return String(first)
}
/// Last character of string (if applicable).
///
/// "Hello".lastCharacterAsString -> Optional("o")
/// "".lastCharacterAsString -> nil
///
var lastCharacterAsString: String? {
guard let last = last else { return nil }
return String(last)
}
/// Transforms the string into a slug string.
///
/// "Swift is amazing".toSlug() -> "swift-is-amazing"
///
/// - Returns: The string in slug format.
func toSlug() -> String {
let lowercased = self.lowercased()
let latinized = lowercased.folding(options: .diacriticInsensitive, locale: Locale.current)
let withDashes = latinized.replacingOccurrences(of: " ", with: "-")
let alphanumerics = NSCharacterSet.alphanumerics
var filtered = withDashes.filter {
guard String($0) != "-" else { return true }
guard String($0) != "&" else { return true }
return String($0).rangeOfCharacter(from: alphanumerics) != nil
}
while filtered.lastCharacterAsString == "-" {
filtered = String(filtered.dropLast())
}
while filtered.firstCharacterAsString == "-" {
filtered = String(filtered.dropFirst())
}
return filtered.replacingOccurrences(of: "--", with: "-")
}
/// Removes spaces and new lines in beginning and end of string.
///
/// var str = " \n Hello World \n\n\n"
/// str.trim()
/// print(str) // prints "Hello World"
///
@discardableResult
mutating func trim() -> String {
self = trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
return self
}
/// Loverde Co.: Add mask to a text - Very simple to use
///
/// - Parameter toText: String you want to maks
/// - Parameter mask: Using mask like sharp ##-#####(###)
func applyMask(toText: String, mask: String) -> String {
let toTextNSString = toText as NSString
let maskNSString = mask as NSString
var onOriginal: Int = 0
var onFilter: Int = 0
var onOutput: Int = 0
var outputString = [Character](repeating: "\0", count: maskNSString.length)
var done:Bool = false
while (onFilter < maskNSString.length && !done) {
let filterChar: Character = Character(UnicodeScalar(maskNSString.character(at: onFilter)) ?? UnicodeScalar.init(0))
let originalChar: Character = onOriginal >= toTextNSString.length ? "\0" :
Character(UnicodeScalar(toTextNSString.character(at: onOriginal))!)
switch filterChar {
case "#":
if (originalChar == "\0") {
// We have no more input numbers for the filter. We're done.
done = true
break
}
if (CharacterSet.init(charactersIn: "0123456789").contains(UnicodeScalar(originalChar.unicodeScalarCodePoint())!)) {
outputString[onOutput] = originalChar;
onOriginal += 1
onFilter += 1
onOutput += 1
}else{
onOriginal += 1
}
default:
// Any other character will automatically be inserted for the user as they type (spaces, - etc..) or deleted as they delete if there are more numbers to come.
outputString[onOutput] = filterChar;
onOutput += 1
onFilter += 1
if(originalChar == filterChar) {
onOriginal += 1
}
}
}
if (onOutput < outputString.count){
outputString[onOutput] = "\0" // Cap the output string
}
return String(outputString).replacingOccurrences(of: "\0", with: "")
}
func stringByAddingPercentEncodingForRFC3986() -> String {
let allowedQueryParamAndKey = CharacterSet(charactersIn: ";/?:@&=+$, ").inverted
return addingPercentEncoding(withAllowedCharacters: allowedQueryParamAndKey)!
}
func replaceAll(of pattern: String,
with replacement: String,
options: NSRegularExpression.Options = []) -> String{
do{
let regex = try NSRegularExpression(pattern: pattern, options: [])
let range = NSRange(0..<self.utf16.count)
return regex.stringByReplacingMatches(in: self, options: [],
range: range,
withTemplate: replacement)
} catch {
print("replaceAll error: %@", error)
return self
}
}
}
public extension Character {
func unicodeScalarCodePoint() -> UInt32 {
let characterString = String(self)
let scalars = characterString.unicodeScalars
return scalars[scalars.startIndex].value
}
}

View File

@@ -0,0 +1,30 @@
//
// Copyright (c) 2024 Loverde Co.
//
// 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
extension StringProtocol {
var data: Data { Data(utf8) }
var base64Encoded: Data { data.base64EncodedData() }
var base64Decoded: Data? { Data(base64Encoded: string) }
var string: String { String(self) }
}

View File

@@ -0,0 +1,107 @@
//
// Copyright (c) 2022 Loverde Co.
//
// 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.
#if canImport(UIKit) && os(iOS) || os(macOS)
import UIKit
public extension UIApplication {
/// Application running environment.
///
/// - debug: Application is running in debug mode.
/// - testFlight: Application is installed from Test Flight.
/// - appStore: Application is installed from the App Store.
enum Environment {
/// Application is running in debug mode.
case debug
/// Application is installed from Test Flight.
case testFlight
/// Application is installed from the App Store.
case appStore
}
/// Current inferred app environment.
static var inferredEnvironment: Environment {
#if DEBUG
return .debug
#elseif targetEnvironment(simulator)
return .debug
#else
if Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") != nil {
return .testFlight
}
guard let appStoreReceiptUrl = Bundle.main.appStoreReceiptURL else {
return .debug
}
if appStoreReceiptUrl.lastPathComponent.lowercased() == "sandboxreceipt" {
return .testFlight
}
if appStoreReceiptUrl.path.lowercased().contains("simulator") {
return .debug
}
return .appStore
#endif
}
/// Application name (if applicable).
static var displayName: String? {
return Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
}
/// App current build number (if applicable).
static var buildNumber: String? {
return Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String
}
/// App's current version number (if applicable).
static var version: String? {
return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
}
}
public extension UIApplication {
static func openURL(urlStr: String) {
guard let url = URL(string: urlStr) else { return }
if UIApplication.shared.canOpenURL(url) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
} else {
// App is not installed
guard let storeApp = URL(string: urlStr) else { return }
if #available(iOS 10.0, *) {
UIApplication.shared.open(storeApp, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(storeApp)
}
}
}
}
#endif

View File

@@ -0,0 +1,191 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
import UIKit
#if os(iOS) || os(macOS)
// MARK: - Properties
public extension UIButton {
/// Image of disabled state for button; also inspectable from Storyboard.
var imageForDisabled: UIImage? {
get {
return image(for: .disabled)
}
set {
setImage(newValue, for: .disabled)
}
}
/// Image of highlighted state for button; also inspectable from Storyboard.
var imageForHighlighted: UIImage? {
get {
return image(for: .highlighted)
}
set {
setImage(newValue, for: .highlighted)
}
}
/// Image of normal state for button; also inspectable from Storyboard.
var imageForNormal: UIImage? {
get {
return image(for: .normal)
}
set {
setImage(newValue, for: .normal)
}
}
/// Image of selected state for button; also inspectable from Storyboard.
var imageForSelected: UIImage? {
get {
return image(for: .selected)
}
set {
setImage(newValue, for: .selected)
}
}
/// Title color of disabled state for button; also inspectable from Storyboard.
var titleColorForDisabled: UIColor? {
get {
return titleColor(for: .disabled)
}
set {
setTitleColor(newValue, for: .disabled)
}
}
/// Title color of highlighted state for button; also inspectable from Storyboard.
var titleColorForHighlighted: UIColor? {
get {
return titleColor(for: .highlighted)
}
set {
setTitleColor(newValue, for: .highlighted)
}
}
/// Title color of normal state for button; also inspectable from Storyboard.
var titleColorForNormal: UIColor? {
get {
return titleColor(for: .normal)
}
set {
setTitleColor(newValue, for: .normal)
}
}
/// Title color of selected state for button; also inspectable from Storyboard.
var titleColorForSelected: UIColor? {
get {
return titleColor(for: .selected)
}
set {
setTitleColor(newValue, for: .selected)
}
}
/// Title of disabled state for button; also inspectable from Storyboard.
var titleForDisabled: String? {
get {
return title(for: .disabled)
}
set {
setTitle(newValue, for: .disabled)
}
}
/// Title of highlighted state for button; also inspectable from Storyboard.
var titleForHighlighted: String? {
get {
return title(for: .highlighted)
}
set {
setTitle(newValue, for: .highlighted)
}
}
/// Title of normal state for button; also inspectable from Storyboard.
var titleForNormal: String? {
get {
return title(for: .normal)
}
set {
setTitle(newValue, for: .normal)
}
}
/// Title of selected state for button; also inspectable from Storyboard.
var titleForSelected: String? {
get {
return title(for: .selected)
}
set {
setTitle(newValue, for: .selected)
}
}
}
// MARK: - Methods
public extension UIButton {
private var states: [UIControl.State] {
return [.normal, .selected, .highlighted, .disabled]
}
/// Set image for all states.
///
/// - Parameter image: UIImage.
func setImageForAllStates(_ image: UIImage) {
states.forEach { setImage(image, for: $0) }
}
/// Set title color for all states.
///
/// - Parameter color: UIColor.
func setTitleColorForAllStates(_ color: UIColor) {
states.forEach { setTitleColor(color, for: $0) }
}
/// Set title for all states.
///
/// - Parameter title: title string.
func setTitleForAllStates(_ title: String) {
states.forEach { setTitle(title, for: $0) }
}
/// Center align title text and image on UIButton
///
/// - Parameter spacing: spacing between UIButton title text and UIButton Image.
func centerTextAndImage(spacing: CGFloat) {
let insetAmount = spacing / 2
imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: insetAmount)
titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: -insetAmount)
contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
}
}
#endif

View File

@@ -0,0 +1,259 @@
//
// Copyright (c) 2019 Loverde Co.
//
// 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
#if os(iOS) || os(macOS)
import UIKit
public extension UICollectionView {
static var identifier: String {
return "id"+String(describing: self)
}
/// Index path of last item in collectionView.
var indexPathForLastItem: IndexPath? {
return indexPathForLastItem(inSection: lastSection)
}
/// Index of last section in collectionView.
var lastSection: Int {
return numberOfSections > 0 ? numberOfSections - 1 : 0
}
func setupCollectionView(flowLayout: UICollectionViewFlowLayout = UICollectionViewFlowLayout(),
spacings: CGFloat = 0,
direction: UICollectionView.ScrollDirection = .horizontal,
edgesInset: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0),
allowMulpleSelection: Bool = false,
automaticSize: CGSize? = nil) {
let layout: UICollectionViewFlowLayout = flowLayout
layout.sectionInset = edgesInset
layout.minimumInteritemSpacing = spacings
layout.minimumLineSpacing = spacings
layout.scrollDirection = direction
if let automaticSize = automaticSize {
layout.estimatedItemSize = automaticSize
}
self.contentInsetAdjustmentBehavior = .always
self.collectionViewLayout = layout
self.allowsMultipleSelection = allowMulpleSelection
}
/// Reload data with a completion handler.
///
/// - Parameter completion: completion handler to run after reloadData finishes.
func reloadData(_ completion: @escaping () -> Void) {
UIView.animate(withDuration: 0, animations: {
self.reloadData()
}, completion: { _ in
completion()
})
}
/// Number of all items in all sections of collectionView.
///
/// - Returns: The count of all rows in the collectionView.
func numberOfItems() -> Int {
var section = 0
var itemsCount = 0
while section < numberOfSections {
itemsCount += numberOfItems(inSection: section)
section += 1
}
return itemsCount
}
/// IndexPath for last item in section.
///
/// - Parameter section: section to get last item in.
/// - Returns: optional last indexPath for last item in section (if applicable).
func indexPathForLastItem(inSection section: Int) -> IndexPath? {
guard section >= 0 else {
return nil
}
guard section < numberOfSections else {
return nil
}
guard numberOfItems(inSection: section) > 0 else {
return IndexPath(item: 0, section: section)
}
return IndexPath(item: numberOfItems(inSection: section) - 1, section: section)
}
/// Safely scroll to possibly invalid IndexPath.
///
/// - Parameters:
/// - indexPath: Target IndexPath to scroll to.
/// - scrollPosition: Scroll position.
/// - animated: Whether to animate or not.
func safeScrollToItem(at indexPath: IndexPath, at scrollPosition: UICollectionView.ScrollPosition, animated: Bool) {
guard indexPath.item >= 0,
indexPath.section >= 0,
indexPath.section < numberOfSections,
indexPath.item < numberOfItems(inSection: indexPath.section) else {
return
}
scrollToItem(at: indexPath, at: scrollPosition, animated: animated)
}
/// Check whether IndexPath is valid within the CollectionView.
///
/// - Parameter indexPath: An IndexPath to check.
/// - Returns: Boolean value for valid or invalid IndexPath.
func isValidIndexPath(_ indexPath: IndexPath) -> Bool {
return indexPath.section >= 0 &&
indexPath.item >= 0 &&
indexPath.section < numberOfSections &&
indexPath.item < numberOfItems(inSection: indexPath.section)
}
}
public enum CollectionViewFlowLayoutSpacingMode {
case fixed(spacing: CGFloat)
case overlap(visibleOffset: CGFloat)
}
/// CollectionViewFlowLayout create a centralized content with pagination.
///
open class CollectionViewFlowLayout: UICollectionViewFlowLayout {
private struct LayoutState {
var size: CGSize
var direction: UICollectionView.ScrollDirection
func isEqual(_ otherState: LayoutState) -> Bool {
return self.size.equalTo(otherState.size) && self.direction == otherState.direction
}
}
private var state = LayoutState(size: CGSize.zero, direction: .horizontal)
open var sideItemScale: CGFloat = 0.6
open var sideItemAlpha: CGFloat = 0.6
open var sideItemShift: CGFloat = 0.0
open var spacingMode = CollectionViewFlowLayoutSpacingMode.fixed(spacing: 40)
override open func prepare() {
super.prepare()
let currentState = LayoutState(size: self.collectionView?.bounds.size ?? CGSize.zero,
direction: self.scrollDirection)
if !self.state.isEqual(currentState) {
self.setupCollectionView()
self.updateLayout()
self.state = currentState
}
}
private func setupCollectionView() {
guard let collectionView = self.collectionView else { return }
if collectionView.decelerationRate != UIScrollView.DecelerationRate.fast {
collectionView.decelerationRate = UIScrollView.DecelerationRate.fast
}
}
private func updateLayout() {
guard let collectionView = self.collectionView else { return }
let collectionSize = collectionView.bounds.size
let isHorizontal = (self.scrollDirection == .horizontal)
let yInset = (collectionSize.height - self.itemSize.height) / 2
let xInset = (collectionSize.width - self.itemSize.width) / 2
self.sectionInset = UIEdgeInsets.init(top: yInset, left: xInset, bottom: yInset, right: xInset)
let side = isHorizontal ? self.itemSize.width : self.itemSize.height
let scaledItemOffset = (side - side*self.sideItemScale) / 2
switch self.spacingMode {
case .fixed(let spacing):
self.minimumLineSpacing = spacing - scaledItemOffset
case .overlap(let visibleOffset):
let fullSizeSideItemOverlap = visibleOffset + scaledItemOffset
let inset = isHorizontal ? xInset : yInset
self.minimumLineSpacing = inset - fullSizeSideItemOverlap
}
}
override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let superAttributes = super.layoutAttributesForElements(in: rect),
let attributes = NSArray(array: superAttributes, copyItems: true) as? [UICollectionViewLayoutAttributes]
else { return nil }
return attributes.map({ self.transformLayoutAttributes($0) })
}
private func transformLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
guard let collectionView = self.collectionView else { return attributes }
let isHorizontal = (self.scrollDirection == .horizontal)
let collectionCenter = isHorizontal ? collectionView.frame.size.width/2 : collectionView.frame.size.height/2
let offset = isHorizontal ? collectionView.contentOffset.x : collectionView.contentOffset.y
let normalizedCenter = (isHorizontal ? attributes.center.x : attributes.center.y) - offset
let maxDistance = (isHorizontal ? self.itemSize.width : self.itemSize.height) + self.minimumLineSpacing
let distance = min(abs(collectionCenter - normalizedCenter), maxDistance)
let ratio = (maxDistance - distance)/maxDistance
let alpha = ratio * (1 - self.sideItemAlpha) + self.sideItemAlpha
let scale = ratio * (1 - self.sideItemScale) + self.sideItemScale
let shift = (1 - ratio) * self.sideItemShift
attributes.alpha = alpha
attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)
attributes.zIndex = Int(alpha * 10)
if isHorizontal {
attributes.center.y += shift
} else {
attributes.center.x += shift
}
return attributes
}
override open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint,
withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView, !collectionView.isPagingEnabled,
let layoutAttributes = self.layoutAttributesForElements(in: collectionView.bounds)
else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset) }
let isHorizontal = (self.scrollDirection == .horizontal)
let midSide = (isHorizontal ? collectionView.bounds.size.width : collectionView.bounds.size.height) / 2
let proposedContentOffsetCenterOrigin = (isHorizontal ? proposedContentOffset.x : proposedContentOffset.y) + midSide
var targetContentOffset: CGPoint
if isHorizontal {
let closest = layoutAttributes.sorted {abs($0.center.x-proposedContentOffsetCenterOrigin) < abs($1.center.x-proposedContentOffsetCenterOrigin)}.first ?? UICollectionViewLayoutAttributes()
targetContentOffset = CGPoint(x: floor(closest.center.x - midSide), y: proposedContentOffset.y)
} else {
let closest = layoutAttributes.sorted {abs($0.center.y-proposedContentOffsetCenterOrigin) < abs($1.center.y-proposedContentOffsetCenterOrigin)}.first ?? UICollectionViewLayoutAttributes()
targetContentOffset = CGPoint(x: proposedContentOffset.x, y: floor(closest.center.y - midSide))
}
return targetContentOffset
}
}
#endif

View File

@@ -0,0 +1,106 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
import UIKit
#if os(iOS) || os(macOS)
public 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 }
convenience init(hex: String) {
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
var alpha: CGFloat = 1.0
var hex: String = hex
if hex.hasPrefix("#") {
hex = String(hex.dropFirst())
}
let scanner = Scanner(string: hex)
var hexValue: CUnsignedLongLong = 0
if scanner.scanHexInt64(&hexValue) {
switch (hex.count) {
case 3:
red = CGFloat((hexValue & 0xF00) >> 8) / 15.0
green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0
blue = CGFloat(hexValue & 0x00F) / 15.0
case 4:
red = CGFloat((hexValue & 0xF000) >> 12) / 15.0
green = CGFloat((hexValue & 0x0F00) >> 8) / 15.0
blue = CGFloat((hexValue & 0x00F0) >> 4) / 15.0
alpha = CGFloat(hexValue & 0x000F) / 15.0
case 6:
red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0
green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0
blue = CGFloat(hexValue & 0x0000FF) / 255.0
case 8:
red = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0
green = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0
blue = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0
alpha = CGFloat(hexValue & 0x000000FF) / 255.0
default:
print("Invalid RGB string, number of characters after '#' should be either 3, 4, 6 or 8", terminator: "")
}
} else {
print("Scan hex error")
}
self.init(red:red, green:green, blue:blue, alpha:alpha)
}
var hexString: String? {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
let multiplier = CGFloat(255.999999)
guard self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else {
return nil
}
if alpha == 1.0 {
return String(
format: "#%02lX%02lX%02lX",
Int(red * multiplier),
Int(green * multiplier),
Int(blue * multiplier)
)
}
else {
return String(
format: "#%02lX%02lX%02lX%02lX",
Int(red * multiplier),
Int(green * multiplier),
Int(blue * multiplier),
Int(alpha * multiplier)
)
}
}
}
#endif

View File

@@ -0,0 +1,124 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
import UIKit
#if os(iOS) || os(macOS)
public extension UIDevice {
static var topNotch: CGFloat {
if #available(iOS 11.0, *) {
return LCEssentials.keyWindow?.safeAreaInsets.top ?? 0
}
return 0
}
static var bottomNotch: CGFloat {
if #available(iOS 11.0, *) {
return LCEssentials.keyWindow?.safeAreaInsets.bottom ?? 0
}
return 0
}
static var hasNotch: Bool {
return (self.topNotch > 0 && self.bottomNotch > 0)
}
var modelName: String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
var identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8 , value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
#if swift(>=4.1)
#if targetEnvironment(simulator)
//#if (arch(i386) || arch(x86_64)) && os(iOS)
// this neat trick is found at http://kelan.io/2015/easier-getenv-in-swift/
if let dir = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] {
identifier = dir
}
#endif
#endif
#if os(iOS)
switch identifier {
case "iPod5,1": return "iPod touch (5th generation)"
case "iPod7,1": return "iPod touch (6th generation)"
case "iPod9,1": return "iPod touch (7th generation)"
case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4"
case "iPhone4,1": return "iPhone 4s"
case "iPhone5,1", "iPhone5,2": return "iPhone 5"
case "iPhone5,3", "iPhone5,4": return "iPhone 5c"
case "iPhone6,1", "iPhone6,2": return "iPhone 5s"
case "iPhone7,2": return "iPhone 6"
case "iPhone7,1": return "iPhone 6 Plus"
case "iPhone8,1": return "iPhone 6s"
case "iPhone8,2": return "iPhone 6s Plus"
case "iPhone9,1", "iPhone9,3": return "iPhone 7"
case "iPhone9,2", "iPhone9,4": return "iPhone 7 Plus"
case "iPhone8,4", "iPhone12,8": return "iPhone SE"
case "iPhone10,1", "iPhone10,4": return "iPhone 8"
case "iPhone10,2", "iPhone10,5": return "iPhone 8 Plus"
case "iPhone10,3", "iPhone10,6": return "iPhone X"
case "iPhone11,2": return "iPhone XS"
case "iPhone11,4", "iPhone11,6": return "iPhone XS Max"
case "iPhone11,8": return "iPhone XR"
case "iPhone12,1": return "iPhone 11"
case "iPhone12,3": return "iPhone 11 Pro"
case "iPhone12,5": return "iPhone 11 Pro Max"
case "iPhone13,1": return "iPhone 12 mini"
case "iPhone13,2": return "iPhone 12"
case "iPhone13,3": return "iPhone 12 Pro"
case "iPhone13,4": return "iPhone 12 Pro Max"
case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return "iPad 2"
case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad 3"
case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad 4"
case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air"
case "iPad5,3", "iPad5,4": return "iPad Air 2"
case "iPad6,11", "iPad6,12": return "iPad 5"
case "iPad7,5", "iPad7,6": return "iPad 6"
case "iPad7,11", "iPad7,12": return "iPad 7"
case "iPad11,4", "iPad11,5": return "iPad Air (3rd generation)"
case "iPad2,5", "iPad2,6", "iPad2,7": return "iPad Mini"
case "iPad4,4", "iPad4,5", "iPad4,6": return "iPad Mini 2"
case "iPad4,7", "iPad4,8", "iPad4,9": return "iPad Mini 3"
case "iPad5,1", "iPad5,2": return "iPad Mini 4"
case "iPad11,1", "iPad11,2": return "iPad Mini 5"
case "iPad6,3", "iPad6,4": return "iPad Pro (9.7-inch)"
case "iPad6,7", "iPad6,8": return "iPad Pro (12.9-inch)"
case "iPad7,1", "iPad7,2": return "iPad Pro (12.9-inch) (2nd generation)"
case "iPad7,3", "iPad7,4": return "iPad Pro (10.5-inch)"
case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4":return "iPad Pro (11-inch)"
case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8":return "iPad Pro (12.9-inch) (3rd generation)"
case "AppleTV5,3": return "Apple TV"
case "AppleTV6,2": return "Apple TV 4K"
case "AudioAccessory1,1": return "HomePod"
case "i386", "x86_64": return "Simulator \(identifier)"
default: return identifier
}
#endif
}
}
#endif

View File

@@ -0,0 +1,186 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
import UIKit
#if os(iOS) || os(macOS)
public extension UIImage {
//Extension Required by RoundedButton to create UIImage from UIColor
func imageWithColor(color: UIColor) -> UIImage {
let rect: CGRect = CGRect(origin: .zero, size: CGSize(width: 1, height: 1))
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), false, 1.0)
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
/** Cria uma cópia de uma imagem fazendo sobreposição de cor.*/
func tintImage(color:UIColor) -> UIImage {
let newImage = self.withRenderingMode(.alwaysTemplate)
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
color.set()
newImage.draw(in: CGRect.init(x: 0.0, y: 0.0, width: self.size.width, height: self.size.height))
let finalImage:UIImage? = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//
return finalImage ?? self
}
///Make transparent color of image - choose a color and range color
func backgroundColorTransparent(initialColor: UIColor, finalColor: UIColor) -> UIImage? {
let image = UIImage(data: self.jpegData(compressionQuality: 1.0)!)!
let rawImageRef: CGImage = image.cgImage!
//let colorMasking: [CGFloat] = [222, 255, 222, 255, 222, 255]
let colorMasking: [CGFloat] = [finalColor.redValue, initialColor.redValue, finalColor.greenValue, initialColor.greenValue, finalColor.blueValue, initialColor.blueValue]
UIGraphicsBeginImageContext(image.size);
let maskedImageRef = rawImageRef.copy(maskingColorComponents: colorMasking)
UIGraphicsGetCurrentContext()?.translateBy(x: 0.0,y: image.size.height)
UIGraphicsGetCurrentContext()?.scaleBy(x: 1.0, y: -1.0)
UIGraphicsGetCurrentContext()?.draw(maskedImageRef!, in: CGRect.init(x: 0, y: 0, width: image.size.width, height: image.size.height))
let result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result
}
/// Creates a circular outline image.
class func outlinedEllipse(size: CGSize, color: UIColor, lineWidth: CGFloat = 1.0) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.setStrokeColor(color.cgColor)
context.setLineWidth(lineWidth)
// Inset the rect to account for the fact that strokes are
// centred on the bounds of the shape.
let rect = CGRect(origin: .zero, size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)
context.addEllipse(in: rect)
context.strokePath()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
func resizeImage(newWidth: CGFloat) -> UIImage {
let scale = newWidth / self.size.width
let newHeight = self.size.height * scale
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
/// Verifica se a imagem instanciada é uma animação.
/// - Returns: O retorno será 'true' para imagens animadas e 'false' para imagens normais.
func isAnimated() -> Bool {
if ((self.images?.count ?? 0) > 1) {
return true
}
return false
}
/// Cria o `thumbnail` da imagem.
/// - Parameter maxPixelSize: Máxima dimensão que o thumb deve ter.
/// - Returns: Retorna uma nova instância cópia do objeto imagem original.
func createThumbnail(_ maxPixelSize:UInt) -> UIImage {
if self.size.width == 0 || self.size.height == 0 || self.isAnimated() {
return self
}
if let data = self.pngData() {
let imageSource:CGImageSource = CGImageSourceCreateWithData(data as CFData, nil)!
//
var options: [NSString:Any] = Dictionary()
options[kCGImageSourceThumbnailMaxPixelSize] = maxPixelSize
options[kCGImageSourceCreateThumbnailFromImageAlways] = true
//
if let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) {
let finalImage = UIImage.init(cgImage: scaledImage)
return finalImage
}
}
return self
}
/// Aplica uma máscara na imagem base, gerando uma nova imagem 'vazada'. A máscara deve conter canal alpha, que definirá a visibilidade final da imagem resultante.
func maskWithAlphaImage(maskImage:UIImage) -> UIImage {
if self.cgImage == nil || maskImage.cgImage == nil || self.size.width == 0 || self.size.height == 0 || self.isAnimated() {
return self
}
let filterName = "CIBlendWithAlphaMask"
let inputImage = CIImage.init(cgImage: self.cgImage!)
let inputMaskImage = CIImage.init(cgImage: maskImage.cgImage!)
let context:CIContext = CIContext.init()
if let ciFilter:CIFilter = CIFilter.init(name: filterName) {
ciFilter.setValue(inputImage, forKey: kCIInputImageKey)
ciFilter.setValue(inputMaskImage, forKey: kCIInputMaskImageKey)
//
if let outputImage:CIImage = ciFilter.outputImage {
let cgimg:CGImage = context.createCGImage(outputImage, from: outputImage.extent)!
let newImage:UIImage = UIImage.init(cgImage: cgimg, scale: self.scale, orientation: self.imageOrientation)
return newImage
}
}
return self
}
/// Create a new image from a base 64 string.
///
/// - Parameters:
/// - base64String: a base-64 `String`, representing the image
/// - scale: The scale factor to assume when interpreting the image data created from the base-64 string. Applying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the `size` property.
convenience init?(base64String: String, scale: CGFloat = 1.0) {
guard let data = Data(base64Encoded: base64String) else { return nil }
self.init(data: data, scale: scale)
}
@MainActor
convenience init(view: UIView) {
UIGraphicsBeginImageContext(view.frame.size)
view.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.init(cgImage: image!.cgImage!)
}
}
#endif

View File

@@ -0,0 +1,69 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
import UIKit
#if os(iOS) || os(macOS)
@MainActor
public extension UIImageView {
func changeColorOfImage( _ color: UIColor, image: UIImage? ) -> UIImageView {
let origImage = image
let tintedImage = origImage?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
self.image = tintedImage
self.tintColor = color
return self
}
var encodeToBase64: String? {
let jpegCompressionQuality: CGFloat = 0.6
if let base64String = self.image?.jpegData(compressionQuality: jpegCompressionQuality) {
let strBase64 = base64String.base64EncodedString(options: .endLineWithLineFeed)
return strBase64
}
return nil
}
func addAspectRatioConstraint() {
removeAspectRatioConstraint()
if let image = self.image, image.size.height > 0 {
let aspectRatio = image.size.width / image.size.height
let constraint = NSLayoutConstraint(item: self, attribute: .width,
relatedBy: .equal,
toItem: self, attribute: .height,
multiplier: aspectRatio, constant: 0.0)
addConstraint(constraint)
}
}
func removeAspectRatioConstraint() {
for constraint in self.constraints
where (constraint.firstItem as? UIImageView) == self
&& (constraint.secondItem as? UIImageView) == self {
removeConstraint(constraint)
}
}
}
#endif

View File

@@ -0,0 +1,48 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
import UIKit
#if os(iOS) || os(macOS)
public extension UILabel {
func lineNumbers() -> Int{
let textSize = CGSize(width: self.frame.size.width, height: CGFloat(Float.infinity))
let rHeight = lroundf(Float(self.sizeThatFits(textSize).height))
let charSize = lroundf(Float(self.font.lineHeight))
let lineCount = rHeight/charSize
return lineCount
}
var getEstimatedHeight: CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.attributedText = attributedText
label.sizeToFit()
return label.frame.height
}
}
#endif

View File

@@ -0,0 +1,93 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
#if os(iOS) || os(macOS)
import UIKit
import QuartzCore
public extension UINavigationController {
/// Pop ViewController with completion handler.
///
/// - Parameters:
/// - animated: Set this value to true to animate the transition (default is true).
/// - completion: optional completion handler (default is nil).
func popViewController(animated: Bool = true, _ completion: (() -> Void)? = nil) {
// https://github.com/cotkjaer/UserInterface/blob/master/UserInterface/UIViewController.swift
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
popViewController(animated: animated)
CATransaction.commit()
}
/// Pop To a ViewController with completion handler.
///
/// - Parameters:
/// - animated: Set this value to true to animate the transition (default is true).
/// - completion: optional completion handler (default is nil).
func popToViewController(_ viewController: UIViewController, animated: Bool = true, _ completion: (() -> Void)? = nil) {
// https://github.com/cotkjaer/UserInterface/blob/master/UserInterface/UIViewController.swift
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
popToViewController(viewController, animated: animated)
CATransaction.commit()
}
/// Push ViewController with completion handler.
///
/// - Parameters:
/// - viewController: viewController to push.
/// - completion: optional completion handler (default is nil).
func pushViewController(_ viewController: UIViewController, animated: Bool = true, completion: (() -> Void)? = nil) {
// https://github.com/cotkjaer/UserInterface/blob/master/UserInterface/UIViewController.swift
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
pushViewController(viewController, animated: animated)
CATransaction.commit()
}
#if !os(tvOS)
/// Pushes a view controller while hiding or showing the bottom bar.
///
/// - Parameters:
/// - viewController: The view controller to push.
/// - hidesBottomBar: If `true`, hides the bottom bar (e.g. tab bar).
/// - animated: Specify `true` to animate the transition.
func pushViewController(_ viewController: UIViewController, hidesBottomBar: Bool = false, animated: Bool = true) {
viewController.hidesBottomBarWhenPushed = hidesBottomBar
pushViewController(viewController, animated: animated)
}
#endif
/// Make navigation controller's navigation bar transparent.
///
/// - Parameter tint: tint color (default is .white).
func makeTransparent(withTint tint: UIColor = .white) {
navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationBar.shadowImage = UIImage()
navigationBar.isTranslucent = true
navigationBar.tintColor = tint
navigationBar.titleTextAttributes = [.foregroundColor: tint]
}
}
#endif

View File

@@ -0,0 +1,46 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
#if os(iOS) || os(macOS)
import UIKit
public extension UIResponder {
var getParentViewController: UIViewController? {
if next is UIViewController {
return next as? UIViewController
} else {
if next != nil {
return next?.getParentViewController
}
else { return nil }
}
}
}
//HOW TO USE
//let vc = UIViewController()
//let view = UIView()
//vc.view.addSubview(view)
//view.getParentViewController //provide reference to vc
#endif

View File

@@ -0,0 +1,137 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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.
#if canImport(UIKit) && !os(watchOS)
import UIKit
// MARK: - Methods
public extension UIScrollView {
enum orientation {
case horizontal, vertical
}
/// Takes a snapshot of an entire ScrollView
///
/// AnySubclassOfUIScroolView().snapshot
/// UITableView().snapshot
///
/// - Returns: Snapshot as UIimage for rendered ScrollView
var snapshot: UIImage? {
// Original Source: https://gist.github.com/thestoics/1204051
UIGraphicsBeginImageContextWithOptions(contentSize, false, 0)
defer {
UIGraphicsEndImageContext()
}
guard let context = UIGraphicsGetCurrentContext() else { return nil }
let previousFrame = frame
frame = CGRect(origin: frame.origin, size: contentSize)
layer.render(in: context)
frame = previousFrame
return UIGraphicsGetImageFromCurrentImageContext()
}
/// The currently visible region of the scroll view.
var visibleRect: CGRect {
let contentWidth = contentSize.width - contentOffset.x
let contentHeight = contentSize.height - contentOffset.y
return CGRect(origin: contentOffset,
size: CGSize(width: min(min(bounds.size.width, contentSize.width), contentWidth),
height: min(min(bounds.size.height, contentSize.height), contentHeight)))
}
var offsetInPage: CGFloat {
let page = contentOffset.y / frame.size.height
return page - floor(page)
}
}
public extension UIScrollView {
/// Scroll up one page of the scroll view.
/// If `isPagingEnabled` is `true`, the previous page location is used.
/// - Parameter animated: `true` to animate the transition at a constant velocity to the new offset, `false` to make
/// the transition immediate.
func scrollUp(animated: Bool = true) {
let minY = -contentInset.top
var y = max(minY, contentOffset.y - bounds.height)
#if !os(tvOS)
if isPagingEnabled,
bounds.height != 0 {
let page = max(0, ((y + contentInset.top) / bounds.height).rounded(.down))
y = max(minY, page * bounds.height - contentInset.top)
}
#endif
setContentOffset(CGPoint(x: contentOffset.x, y: y), animated: animated)
}
/// Scroll left one page of the scroll view.
/// If `isPagingEnabled` is `true`, the previous page location is used.
/// - Parameter animated: `true` to animate the transition at a constant velocity to the new offset, `false` to make
/// the transition immediate.
func scrollLeft(animated: Bool = true) {
let minX = -contentInset.left
var x = max(minX, contentOffset.x - bounds.width)
#if !os(tvOS)
if isPagingEnabled,
bounds.width != 0 {
let page = ((x + contentInset.left) / bounds.width).rounded(.down)
x = max(minX, page * bounds.width - contentInset.left)
}
#endif
setContentOffset(CGPoint(x: x, y: contentOffset.y), animated: animated)
}
/// Scroll down one page of the scroll view.
/// If `isPagingEnabled` is `true`, the next page location is used.
/// - Parameter animated: `true` to animate the transition at a constant velocity to the new offset, `false` to make
/// the transition immediate.
func scrollDown(animated: Bool = true) {
let maxY = max(0, contentSize.height - bounds.height) + contentInset.bottom
var y = min(maxY, contentOffset.y + bounds.height)
#if !os(tvOS)
if isPagingEnabled,
bounds.height != 0 {
let page = ((y + contentInset.top) / bounds.height).rounded(.down)
y = min(maxY, page * bounds.height - contentInset.top)
}
#endif
setContentOffset(CGPoint(x: contentOffset.x, y: y), animated: animated)
}
/// Scroll right one page of the scroll view.
/// If `isPagingEnabled` is `true`, the next page location is used.
/// - Parameter animated: `true` to animate the transition at a constant velocity to the new offset, `false` to make
/// the transition immediate.
func scrollRight(animated: Bool = true) {
let maxX = max(0, contentSize.width - bounds.width) + contentInset.right
var x = min(maxX, contentOffset.x + bounds.width)
#if !os(tvOS)
if isPagingEnabled,
bounds.width != 0 {
let page = ((x + contentInset.left) / bounds.width).rounded(.down)
x = min(maxX, page * bounds.width - contentInset.left)
}
#endif
setContentOffset(CGPoint(x: x, y: contentOffset.y), animated: animated)
}
}
#endif

View File

@@ -0,0 +1,99 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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
import UIKit
#if os(iOS)
// MARK: - Initializers
@available(iOS 9.0, *)
public extension UIStackView {
convenience init(arrangedSubviews: [UIView]? = nil,
axis: NSLayoutConstraint.Axis = .vertical,
spacing: CGFloat = 0.0,
alignment: UIStackView.Alignment = .fill,
distribution: UIStackView.Distribution = .fill,
layoutMargins: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0),
isMarginsRelative: Bool = true) {
if let arrangedSubviews = arrangedSubviews {
self.init(arrangedSubviews: arrangedSubviews)
} else {
self.init()
}
self.axis = axis
self.spacing = spacing
self.alignment = alignment
self.distribution = distribution
self.layoutMargins = layoutMargins
self.isLayoutMarginsRelativeArrangement = isMarginsRelative
}
func addArrangedSubviews(_ views: [UIView], translateAutoresizing: Bool = false) {
views.forEach { subview in
addArrangedSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = translateAutoresizing
}
}
func removeAllArrangedSubviews(deactivateConstraints: Bool = true) {
arrangedSubviews.forEach {
removeSubview(view: $0, deactivateConstraints: deactivateConstraints)
}
}
/// Remove specific view from it
///
/// - Parameter view: view to be removed.
func removeSubview(view: UIView, deactivateConstraints: Bool = true) {
removeArrangedSubview(view)
if deactivateConstraints {
NSLayoutConstraint.deactivate(view.constraints)
}
view.removeFromSuperview()
}
private func addSpace(height: CGFloat? = nil, width: CGFloat? = nil, backgroundColor: UIColor = .clear) {
let spaceView = UIView()
spaceView.backgroundColor = backgroundColor
spaceView.translatesAutoresizingMaskIntoConstraints = false
if let height = height {
spaceView.setHeight(size: height)
}
if let width = width {
spaceView.setWidth(size: width)
}
addArrangedSubview(spaceView)
}
func addSpace(_ size: CGFloat, backgroundColor: UIColor = .clear) {
switch self.axis {
case .vertical:
addSpace(height: size, backgroundColor: backgroundColor)
case .horizontal:
addSpace(width: size, backgroundColor: backgroundColor)
default: break
}
}
}
#endif

View File

@@ -0,0 +1,173 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
#if os(iOS) || os(macOS)
import UIKit
public class CustomTabBadge: UILabel {
public init(font: UIFont = UIFont(name: "Helvetica-Light", size: 11) ?? UIFont()) {
super.init(frame: .zero)
self.font = font
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
public extension UITabBarController {
func setBadges(badgeValues: [Int], font: UIFont = UIFont(name: "Helvetica-Light", size: 11) ?? UIFont()) {
for view in self.tabBar.subviews {
if view is CustomTabBadge {
view.removeFromSuperview()
}
}
for index in 0...badgeValues.count-1 {
if badgeValues[index] != 0 {
addBadge(index: index,
value: badgeValues[index],
color: .red,
font: font)
}
}
}
func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
let badgeView = CustomTabBadge(font: font)
badgeView.clipsToBounds = true
badgeView.textColor = UIColor.white
badgeView.textAlignment = .center
badgeView.text = String(value)
badgeView.backgroundColor = color
badgeView.tag = index
tabBar.addSubview(badgeView)
self.positionBadges()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.positionBadges()
}
// Positioning
func positionBadges() {
var tabbarButtons = self.tabBar.subviews.filter { (view: UIView) -> Bool in
return view.isUserInteractionEnabled // only UITabBarButton are userInteractionEnabled
}
tabbarButtons = tabbarButtons.sorted(by: { $0.frame.origin.x < $1.frame.origin.x })
for view in self.tabBar.subviews {
if view is CustomTabBadge {
if let badgeView = view as? CustomTabBadge {
self.positionBadge(badgeView: badgeView, items: tabbarButtons, index: badgeView.tag)
}
}
}
}
func positionBadge(badgeView: UIView, items: [UIView], index: Int) {
let itemView = items[index]
let center = itemView.center
let xOffset: CGFloat = 10
let yOffset: CGFloat = -14
badgeView.frame.size = CGSize(width: 17, height: 17)
badgeView.center = CGPoint(x: center.x + xOffset, y: center.y + yOffset)
badgeView.layer.cornerRadius = badgeView.bounds.width/2
tabBar.bringSubviewToFront(badgeView)
}
func setSelectedView(atIndex: Int, withAnimation: Bool = false, completion:(()->())?){
if let navController = self.viewControllers![atIndex] as? UINavigationController {
navController.popToRootViewController(animated: false)
}
if withAnimation {
animateToTab(toIndex: atIndex)
}else{
self.selectedIndex = atIndex
}
completion?()
}
func setSelectedView(withNoPop atIndex: Int, withAnimation: Bool = false, completion:(()->())?) {
//self.viewControllers?[atIndex].viewWillAppear(true)
if withAnimation {
animateToTab(toIndex: atIndex)
}else{
self.selectedIndex = atIndex
}
completion?()
}
func changeViewControllerToItem(withViewController: UIViewController, Item: Int){
guard let navController = self.viewControllers?[Item] as? UINavigationController else{ return }
navController.setViewControllers([withViewController], animated: true)
self.setSelectedView(atIndex: Item, completion: nil)
}
func animateToTab(toIndex: Int) {
let tabViewControllers = viewControllers!
let fromView = selectedViewController!.view
let toView = tabViewControllers[toIndex].view
let fromIndex = self.selectedIndex //tabViewControllers.index(of: selectedViewController!)
guard fromIndex != toIndex else {return}
// Add the toView to the tab bar view
fromView?.superview!.addSubview(toView!)
// Position toView off screen (to the left/right of fromView)
let screenWidth = UIScreen.main.bounds.size.width;
let scrollRight = toIndex > fromIndex;
let offset = (scrollRight ? screenWidth : -screenWidth)
toView?.center = CGPoint(x: (fromView?.center.x)! + offset, y: (toView?.center.y)!)
// Disable interaction during animation
view.isUserInteractionEnabled = false
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveEaseOut, animations: {
// Slide the views by -offset
fromView?.center = CGPoint(x: (fromView?.center.x)! - offset, y: (fromView?.center.y)!);
toView?.center = CGPoint(x: (toView?.center.x)! - offset, y: (toView?.center.y)!);
}, completion: { finished in
// Remove the old view from the tabbar view.
fromView?.removeFromSuperview()
self.selectedIndex = toIndex
self.view.isUserInteractionEnabled = true
})
}
}
#endif

View File

@@ -0,0 +1,151 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
import UIKit
#if os(iOS) || os(macOS)
//MARK: - UITableView Animation Cell
public typealias UITableViewCellAnimation = (UITableViewCell, IndexPath, UITableView) -> Void
public class UITableViewAnimator {
private let animation: UITableViewCellAnimation
public init(animation: @escaping UITableViewCellAnimation) {
self.animation = animation
}
public func animate(cell: UITableViewCell, at indexPath: IndexPath, in tableView: UITableView) {
animation(cell, indexPath, tableView)
}
}
public extension UITableView {
/// Reload data with a completion handler.
///
/// - Parameter completion: completion handler to run after reloadData finishes.
func reloadData(_ completion: @escaping () -> Void) {
UIView.animate(withDuration: 0, animations: {
self.reloadData()
}, completion: { _ in
completion()
})
}
/// Loverde Co.: Animate cell to up with fade - See file example to know how to use
///
func makeMoveUpWithFadeAnimation(rowHeight: CGFloat, duration: TimeInterval, delayFactor: TimeInterval) -> UITableViewCellAnimation {
return { cell, indexPath, _ in
cell.transform = CGAffineTransform(translationX: 0, y: rowHeight * 1.4)
cell.alpha = 0
UIView.animate(
withDuration: duration,
delay: delayFactor * Double(indexPath.row),
options: [.curveEaseInOut],
animations: {
cell.transform = CGAffineTransform(translationX: 0, y: 0)
cell.alpha = 1
})
}
}
/// Dequeue reusable UITableViewCell using class name
///
/// - Parameter name: UITableViewCell type
/// - Returns: UITableViewCell object with associated class name.
func dequeueReusableCell<T: UITableViewCell>(withClass name: T.Type) -> T {
guard let cell = dequeueReusableCell(withIdentifier: String(describing: name)) as? T else {
fatalError("Couldn't find UITableViewCell for \(String(describing: name)), make sure the cell is registered with table view")
}
return cell
}
/// Dequeue reusable UITableViewCell using class name for indexPath
///
/// - Parameters:
/// - name: UITableViewCell type.
/// - indexPath: location of cell in tableView.
/// - Returns: UITableViewCell object with associated class name.
func dequeueReusableCell<T: UITableViewCell>(withClass name: T.Type, for indexPath: IndexPath) -> T {
guard let cell = dequeueReusableCell(withIdentifier: String(describing: name), for: indexPath) as? T else {
fatalError("Couldn't find UITableViewCell for \(String(describing: name)), make sure the cell is registered with table view")
}
return cell
}
/// Dequeue reusable UITableViewHeaderFooterView using class name
///
/// - Parameter name: UITableViewHeaderFooterView type
/// - Returns: UITableViewHeaderFooterView object with associated class name.
func dequeueReusableHeaderFooterView<T: UITableViewHeaderFooterView>(withClass name: T.Type) -> T {
guard let headerFooterView = dequeueReusableHeaderFooterView(withIdentifier: String(describing: name)) as? T else {
fatalError("Couldn't find UITableViewHeaderFooterView for \(String(describing: name)), make sure the view is registered with table view")
}
return headerFooterView
}
func dequeueCell<T: UITableViewCell>(indexPath: IndexPath) -> T {
guard let cell = self.dequeueReusableCell(withIdentifier: T.identifier, for: indexPath) as? T else {
fatalError("Couldn't find UITableViewCell for \(String(describing: T.className)), make sure the view is registered with table view")
}
return cell
}
/// Check whether IndexPath is valid within the tableView
///
/// - Parameter indexPath: An IndexPath to check
/// - Returns: Boolean value for valid or invalid IndexPath
func isValidIndexPath(_ indexPath: IndexPath) -> Bool {
return indexPath.section >= 0 &&
indexPath.row >= 0 &&
indexPath.section < numberOfSections &&
indexPath.row < numberOfRows(inSection: indexPath.section)
}
/// Safely scroll to possibly invalid IndexPath
///
/// - Parameters:
/// - indexPath: Target IndexPath to scroll to
/// - scrollPosition: Scroll position
/// - animated: Whether to animate or not
func safeScrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) {
guard indexPath.section < numberOfSections else { return }
guard indexPath.row < numberOfRows(inSection: indexPath.section) else { return }
scrollToRow(at: indexPath, at: scrollPosition, animated: animated)
}
}
extension UITableViewCell {
public static var identifier: String {
return "id"+String(describing: self)
}
public func prepareDisclosureIndicator() {
for case let button as UIButton in subviews {
let image = button.backgroundImage(for: .normal)?.withRenderingMode(.alwaysTemplate)
button.setBackgroundImage(image, for: .normal)
}
}
}
#endif

View File

@@ -0,0 +1,60 @@
//
// Copyright (c) 2022 Loverde Co.
//
// 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
#if canImport(UIKit) && os(iOS) || os(macOS)
import UIKit
public extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(label: UILabel, textToTouch: String) -> Bool {
let targetRange = NSString().range(of: textToTouch)
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: label.attributedText!)
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = label.bounds.size
textContainer.size = labelSize
let locationOfTouchInLabel = self.location(in: label)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x,
y: locationOfTouchInLabel.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer,
in: textContainer,
fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
#endif

View File

@@ -0,0 +1,63 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
#if os(iOS) || os(macOS)
import UIKit
public extension UITextField {
var placeholderColor: UIColor {
get {
return attributedPlaceholder?.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? UIColor ?? .clear;
}
set {
guard let attributedPlaceholder = attributedPlaceholder else { return; }
let attributes: [NSAttributedString.Key: UIColor] = [.foregroundColor: newValue];
self.attributedPlaceholder = NSAttributedString(string: attributedPlaceholder.string, attributes: attributes);
}
}
/// Add padding to the left of the textfield rect.
///
/// - Parameter padding: amount of padding to apply to the left of the textfield rect.
func addPaddingLeft(_ padding: CGFloat) {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: padding, height: frame.height))
leftView = paddingView
leftViewMode = .always
}
/// Add padding to the left of the textfield rect.
///
/// - Parameters:
/// - image: left image
/// - padding: amount of padding between icon and the left of textfield
func addPaddingLeftIcon(_ image: UIImage, padding: CGFloat) {
let imageView = UIImageView(image: image)
imageView.contentMode = .center
leftView = imageView
leftView?.frame.size = CGSize(width: image.size.width + padding, height: image.size.height)
leftViewMode = .always
}
}
#endif

View File

@@ -0,0 +1,714 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
#if os(iOS) || os(macOS)
import UIKit
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
public enum GradientOrientation {
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint: CGPoint {
return points.startPoint
}
var endPoint: CGPoint {
return points.endPoint
}
var points: GradientPoints {
switch self {
case .topRightBottomLeft:
return (CGPoint(x: 0.0, y: 1.0), CGPoint(x: 1.0, y: 0.0))
case .topLeftBottomRight:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 1, y: 1))
case .horizontal:
return (CGPoint(x: 0.0, y: 0.5), CGPoint(x: 1.0, y: 0.5))
case .vertical:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 0.0, y: 1.0))
}
}
}
public enum EnumBorderSide {
case top, bottom, left, right
}
@MainActor fileprivate weak var kvo_viewReference: UIView?
public extension UIView {
enum AnchorType {
case all
case top
case topToBottom
case bottom
case bottomToTop
case leading
case trailing
case heigth
case width
case centerX
case centerY
case leadingToTrailing
case leadingToTrailingGreaterThanOrEqualTo
case trailingToLeading
case trailingToLeadingGreaterThanOrEqualTo
case topGreaterThanOrEqualTo
case bottomGreaterThanOrEqualTo
case bottomLessThanOrEqualTo
case topToTopGreaterThanOrEqualTo
case left
case right
}
static var className: String {
return String(describing: self)
}
var viewReference: UIView? {
get {
kvo_viewReference
}
set {
kvo_viewReference = newValue
}
}
/// First width constraint for this view.
var widthConstraint: NSLayoutConstraint? {
findConstraint(attribute: .width, for: self)
}
/// First height constraint for this view.
var heightConstraint: NSLayoutConstraint? {
findConstraint(attribute: .height, for: self)
}
/// First leading constraint for this view.
var leadingConstraint: NSLayoutConstraint? {
findConstraint(attribute: .leading, for: self)
}
/// First trailing constraint for this view.
var trailingConstraint: NSLayoutConstraint? {
findConstraint(attribute: .trailing, for: self)
}
/// First top constraint for this view.
var topConstraint: NSLayoutConstraint? {
findConstraint(attribute: .top, for: self)
}
/// First bottom constraint for this view.
var bottomConstraint: NSLayoutConstraint? {
findConstraint(attribute: .bottom, for: self)
}
var centerYConstraints: NSLayoutConstraint? {
findConstraint(attribute: .centerY, for: self)
}
var centerXConstraints: NSLayoutConstraint? {
findConstraint(attribute: .centerX, for: self)
}
var globalPoint: CGPoint? {
return self.superview?.convert(self.frame.origin, to: nil)
}
var globalFrame: CGRect? {
return self.superview?.convert(self.frame, to: nil)
}
/// Loverde Co: Border color of view
var borderColor: UIColor? {
get {
guard let color = layer.borderColor else { return nil }
return UIColor(cgColor: color)
}
set {
guard let color = newValue else {
layer.borderColor = nil
return
}
// Fix React-Native conflict issue
guard String(describing: type(of: color)) != "__NSCFType" else { return }
layer.borderColor = color.cgColor
}
}
/// Loverde Co: Border width of view;
var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set {
layer.borderWidth = newValue
}
}
/// Loverde Co: Corner radius of view;
var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.masksToBounds = true
layer.cornerRadius = newValue
}
}
/// Loverde Co: Check if view is in RTL format.
var isRightToLeft: Bool {
if #available(iOS 10.0, *, tvOS 10.0, *) {
return effectiveUserInterfaceLayoutDirection == .rightToLeft
} else {
return false
}
}
/// Loverde Co: Take screenshot of view (if applicable).
var screenshot: UIImage? {
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, 0)
defer {
UIGraphicsEndImageContext()
}
guard let context = UIGraphicsGetCurrentContext() else { return nil }
layer.render(in: context)
return UIGraphicsGetImageFromCurrentImageContext()
}
/// Get view's parent view controller
var parentViewController: UIViewController? {
weak var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
/// Loverde Co: Get view's absolute position
var absolutePosition: CGRect {
if #available(iOS 15, *), let window = UIApplication.shared.connectedScenes.compactMap({ ($0 as? UIWindowScene)?.keyWindow }).last {
return self.convert(self.bounds, to: window)
} else if #available(iOS 13, *), let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first {
return self.convert(self.bounds, to: window)
} else if let window = UIApplication.shared.keyWindow {
return self.convert(self.bounds, to: window)
}
return .zero
}
/// Loverde Co: transform UIView to UIImage
func asImage() -> UIImage {
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContext(self.frame.size)
self.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImage(cgImage: image!.cgImage!)
}
}
func addSubview(_ subview: UIView, translatesAutoresizingMaskIntoConstraints: Bool = false) {
addSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = translatesAutoresizingMaskIntoConstraints
}
func addSubviews(_ subviews: [UIView], translatesAutoresizingMaskIntoConstraints: Bool = false) {
for subview in subviews {
self.addSubview(subview, translatesAutoresizingMaskIntoConstraints: translatesAutoresizingMaskIntoConstraints)
}
}
/// Returns all the subviews of a given type recursively in the
/// view hierarchy rooted on the view it its called.
///
/// - Parameter ofType: Class of the view to search.
/// - Returns: All subviews with a specified type.
func subviews<T>(ofType _: T.Type) -> [T] {
var views = [T]()
for subview in subviews {
if let view = subview as? T {
views.append(view)
} else if !subview.subviews.isEmpty {
views.append(contentsOf: subview.subviews(ofType: T.self))
}
}
return views
}
func findAView<T>(_ ofType: T.Type) -> T? {
if let finded = subviews.first(where: { $0 is T }) as? T {
return finded
} else {
for view in subviews {
return view.findAView(ofType)
}
}
return nil
}
@discardableResult
func setHeight(size: CGFloat) -> Self {
heightAnchor.constraint(equalToConstant: size).isActive = true
return self
}
@discardableResult
func setHeight(min: CGFloat) -> Self {
heightAnchor.constraint(greaterThanOrEqualToConstant: min).isActive = true
return self
}
@discardableResult
func setWidth(size: CGFloat) -> Self {
widthAnchor.constraint(equalToConstant: size).isActive = true
return self
}
@discardableResult
func setWidth(min: CGFloat) -> Self {
widthAnchor.constraint(greaterThanOrEqualToConstant: min).isActive = true
return self
}
/// Search constraints until we find one for the given view
/// and attribute. This will enumerate ancestors since constraints are
/// always added to the common ancestor.
///
/// - Parameter attribute: the attribute to find.
/// - Parameter at: the view to find.
/// - Returns: matching constraint.
func findConstraint(attribute: NSLayoutConstraint.Attribute, for view: UIView) -> NSLayoutConstraint? {
let constraint = constraints.first {
($0.firstAttribute == attribute && $0.firstItem as? UIView == view) ||
($0.secondAttribute == attribute && $0.secondItem as? UIView == view)
}
return constraint ?? superview?.findConstraint(attribute: attribute, for: view)
}
func constraints(on anchor: NSLayoutYAxisAnchor) -> [NSLayoutConstraint] {
guard let superview = superview else { return [] }
return superview.constraints.filtered(view: self, anchor: anchor)
}
func constraints(on anchor: NSLayoutXAxisAnchor) -> [NSLayoutConstraint] {
guard let superview = superview else { return [] }
return superview.constraints.filtered(view: self, anchor: anchor)
}
func constraints(on anchor: NSLayoutDimension) -> [NSLayoutConstraint] {
guard let superview = superview else { return [] }
return constraints.filtered(view: self, anchor: anchor) + superview.constraints.filtered(view: self, anchor: anchor)
}
func drawCircle(inCoord x: CGFloat,
y: CGFloat,
with radius: CGFloat,
strokeColor: UIColor = UIColor.red,
fillColor: UIColor = UIColor.gray,
isEmpty: Bool = false) -> [String:Any] {
let dotPath = UIBezierPath(ovalIn: CGRect(x: x, y: y, width: radius, height: radius))
let layer = CAShapeLayer()
if !isEmpty {
layer.path = dotPath.cgPath
layer.strokeColor = strokeColor.cgColor
layer.fillColor = fillColor.cgColor
self.layer.addSublayer(layer)
}
return ["path": dotPath,"layer": layer]
}
func applyShadow(color: UIColor, offSet:CGSize,
radius: CGFloat,
opacity: Float,
shouldRasterize: Bool = true,
rasterizationScaleTo: CGFloat = UIScreen.main.scale){
self.layer.masksToBounds = false
self.layer.shadowColor = color.cgColor
self.layer.shadowOffset = offSet
self.layer.shadowRadius = radius
self.layer.shadowOpacity = opacity
self.layer.shouldRasterize = shouldRasterize
self.layer.rasterizationScale = rasterizationScaleTo
}
func removeShadow(){
self.layer.shadowColor = UIColor.clear.cgColor
self.layer.shadowOffset = CGSize.zero
self.layer.shadowRadius = 0.0
self.layer.shadowOpacity = 0.0
}
/// Insert a blur in a view. Must insert on init of it
///
/// - Parameter style: Blur styles available for blur effect objects.
/// - Parameter alpha: The views alpha value.
func insertBlurView(style: UIBlurEffect.Style,
color: UIColor = .black,
alpha: CGFloat = 0.9) {
self.backgroundColor = color
let blurEffect = UIBlurEffect(style: style)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = self.bounds
blurEffectView.alpha = alpha
blurEffectView.autoresizingMask = [
.flexibleWidth, .flexibleHeight
]
self.insertSubview(blurEffectView, at: 0)
}
/**
Fade in a view with a duration
- parameter duration: custom animation duration
*/
func fadeIn(withDuration duration: TimeInterval = 1.0, withDelay delay: TimeInterval = 0, completionHandler:@escaping (Bool) -> ()) {
UIView.animate(withDuration: duration, delay: delay, options: [], animations: {
self.alpha = 1.0
}) { (finished) in
completionHandler(true)
}
}
/**
Fade out a view with a duration
- parameter duration: custom animation duration
*/
func fadeOut(withDuration duration: TimeInterval = 1.0, withDelay delay: TimeInterval = 0, completionHandler:@escaping (Bool) -> ()) {
UIView.animate(withDuration: duration, delay: delay, options: [], animations: {
self.alpha = 0.0
}) { (finished) in
completionHandler(true)
}
}
/**
Set x Position
:param: x CGFloat
*/
func setX(x: CGFloat) {
var frame:CGRect = self.frame
frame.origin.x = x
self.frame = frame
}
/**
Set y Position
:param: y CGFloat
*/
func setY(y: CGFloat) {
var frame:CGRect = self.frame
frame.origin.y = y
self.frame = frame
}
/**
Set Width
:param: width CGFloat
*/
func setFrameWidth(width: CGFloat) {
var frame:CGRect = self.frame
frame.size.width = width
self.frame = frame
}
/**
Set Height
:param: height CGFloat
*/
func setFrameHeight(height: CGFloat) {
var frame:CGRect = self.frame
frame.size.height = height
self.frame = frame
}
func setWidth(_ toView: UIView? = nil, constant: CGFloat, _ multiplier: CGFloat = 0) -> Self {
if let toView = toView {
widthAnchor.constraint(equalTo: toView.widthAnchor, multiplier: multiplier, constant: constant).isActive = true
} else if multiplier == 0 {
widthAnchor.constraint(equalToConstant: constant).isActive = true
}
return self
}
func setHeight(_ toView: UIView? = nil, constant: CGFloat, _ multiplier: CGFloat = 0) -> Self {
if let toView = toView {
heightAnchor.constraint(equalTo: toView.heightAnchor, multiplier: multiplier, constant: constant).isActive = true
} else if multiplier == 0 {
heightAnchor.constraint(equalToConstant: constant).isActive = true
}
return self
}
// - LoverdeCo: Add radius to view
//
//
func setRadius(top: Bool, bottom: Bool, radius: CGFloat = 8){
var maskCorns: CACornerMask = []
var path: UIBezierPath!
if top && bottom {
maskCorns = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner, .layerMinXMaxYCorner]
path = UIBezierPath(roundedRect: (self.bounds),
byRoundingCorners: [.topRight, .topLeft, .bottomLeft, .bottomRight],
cornerRadii: CGSize(width: radius, height: radius))
}else if top && !bottom {
maskCorns = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
path = UIBezierPath(roundedRect: (self.bounds),
byRoundingCorners: [.topRight, .topLeft],
cornerRadii: CGSize(width: radius, height: radius))
}else if bottom && !top {
maskCorns = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
path = UIBezierPath(roundedRect: (self.bounds),
byRoundingCorners: [.bottomLeft, .bottomRight],
cornerRadii: CGSize(width: radius, height: radius))
}
if #available(iOS 11.0, *) {
self.clipsToBounds = true
self.layer.cornerRadius = radius
self.layer.maskedCorners = maskCorns
} else {
self.clipsToBounds = true
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
self.layer.mask = maskLayer
}
}
/// Apply constraint to a View more easily.
///
/// ```swift
/// MyView.setConstraintsTo(parentView: AnotherView, anchorType: AnchorType.leading, value: 10.0)
/// MyView.setConstraintsTo(anchorType: AnchorType.trailing,
/// value: -10.0)
/// .setConstraintsTo(parentView: SuperMegaView,
/// anchorType: AnchorType.bottomToTop,
/// value: -12.0)
/// ```
///
/// - Parameters:
/// - parentView: The View you whant to constraint to.
/// - anchorType: AnchorType you whant to apply"
/// - value: CGFloat value for that constraints to
/// - safeArea: Bool in case you whant top and bottom to be applied to Safe Area Layout Guide. Default is FALSE
/// - Returns:
/// All active constraints you apply.
///
@discardableResult
func setConstraintsTo(parentView: UIView, anchorType: AnchorType, value: CGFloat, safeArea: Bool = false) -> Self {
self.viewReference = parentView
switch anchorType {
case .all:
let topAnchor: NSLayoutConstraint
if #available(iOS 11.0, *) {
topAnchor = self.topAnchor.constraint(equalTo: (safeArea ? parentView.safeAreaLayoutGuide.topAnchor : parentView.topAnchor),
constant: value)
topAnchor.identifier = "topAnchor"
topAnchor.isActive = true
} else {
topAnchor = self.topAnchor.constraint(equalTo: parentView.topAnchor,
constant: value)
topAnchor.identifier = "topAnchor"
topAnchor.isActive = true
}
var negativeValue = value
switch value {
case _ where value < 0:
break
case 0:
break
case _ where value > 0:
negativeValue = -negativeValue
default:
break
}
self.leadingAnchor.constraint(equalTo: parentView.leadingAnchor,
constant: value).isActive = true
self.trailingAnchor.constraint(equalTo: parentView.trailingAnchor,
constant: negativeValue).isActive = true
self.bottomAnchor.constraint(equalTo: parentView.bottomAnchor,
constant: negativeValue).isActive = true
case .top:
if #available(iOS 11.0, *) {
self.topAnchor.constraint(equalTo: (safeArea ? parentView.safeAreaLayoutGuide.topAnchor : parentView.topAnchor),
constant: value).isActive = true
} else {
self.topAnchor.constraint(equalTo: parentView.topAnchor,
constant: value).isActive = true
}
case .topToBottom:
self.topAnchor.constraint(equalTo: parentView.bottomAnchor,
constant: value).isActive = true
case .bottom:
self.bottomAnchor.constraint(equalTo: parentView.bottomAnchor,
constant: value).isActive = true
case .bottomToTop:
self.bottomAnchor.constraint(equalTo: parentView.topAnchor,
constant: value).isActive = true
case .leading:
self.leadingAnchor.constraint(equalTo: parentView.leadingAnchor,
constant: value).isActive = true
case .leadingToTrailing:
self.leadingAnchor.constraint(equalTo: parentView.trailingAnchor,
constant: value).isActive = true
case .leadingToTrailingGreaterThanOrEqualTo:
self.leadingAnchor.constraint(greaterThanOrEqualTo: parentView.trailingAnchor,
constant: value).isActive = true
case .trailing:
self.trailingAnchor.constraint(equalTo: parentView.trailingAnchor,
constant: value).isActive = true
case .trailingToLeading:
self.trailingAnchor.constraint(equalTo: parentView.leadingAnchor,
constant: value).isActive = true
case .trailingToLeadingGreaterThanOrEqualTo:
self.trailingAnchor.constraint(greaterThanOrEqualTo: parentView.leadingAnchor,
constant: value).isActive = true
case .heigth:
self.heightAnchor.constraint(equalTo: parentView.heightAnchor,
constant: value).isActive = true
case .width:
self.widthAnchor.constraint(equalTo: parentView.widthAnchor,
constant: value).isActive = true
case .centerX:
self.centerXAnchor.constraint(equalTo: parentView.centerXAnchor,
constant: value).isActive = true
case .centerY:
self.centerYAnchor.constraint(equalTo: parentView.centerYAnchor,
constant: value).isActive = true
case .topGreaterThanOrEqualTo:
self.topAnchor.constraint(greaterThanOrEqualTo: parentView.bottomAnchor,
constant: value).isActive = true
case .topToTopGreaterThanOrEqualTo:
if #available(iOS 11.0, *) {
self.topAnchor.constraint(greaterThanOrEqualTo: (safeArea ? parentView.safeAreaLayoutGuide.topAnchor : parentView.topAnchor),
constant: value).isActive = true
} else {
self.topAnchor.constraint(greaterThanOrEqualTo: parentView.topAnchor,
constant: value).isActive = true
}
case .bottomGreaterThanOrEqualTo:
self.bottomAnchor.constraint(greaterThanOrEqualTo: parentView.bottomAnchor,
constant: value).isActive = true
case .bottomLessThanOrEqualTo:
self.bottomAnchor.constraint(lessThanOrEqualTo: parentView.bottomAnchor,
constant: value).isActive = true
case .left:
self.leftAnchor.constraint(equalTo: parentView.leftAnchor,
constant: value).isActive = true
case .right:
self.rightAnchor.constraint(equalTo: parentView.rightAnchor,
constant: value).isActive = true
}
return self
}
@discardableResult
func setConstraintsTo(_ parentView: UIView,
_ anchorType: AnchorType,
_ value: CGFloat,
_ safeArea: Bool = false) -> Self {
return self.setConstraintsTo(parentView: parentView,
anchorType: anchorType,
value: value,
safeArea: safeArea)
}
@discardableResult
func setConstraintsTo(anchorType: AnchorType, value: CGFloat, safeArea: Bool = false) -> UIView {
guard let currentView = self.viewReference
else { fatalError("Ops! Faltou o parentView. Utilize setConstraintsTo(parentView... primeiro.") }
self.setConstraintsTo(parentView: currentView, anchorType: anchorType, value: value, safeArea: safeArea)
return self
}
@discardableResult
func setConstraints(_ anchorType: AnchorType, _ value: CGFloat, _ safeArea: Bool = false) -> UIView {
return self.setConstraintsTo(anchorType: anchorType,
value: value,
safeArea: safeArea)
}
func setConstraints(_ toScrollView: UIScrollView, direction: UICollectionView.ScrollDirection = .vertical) {
if self is UIScrollView { fatalError("You cannot apply this to self ScrollView.") }
self.setConstraintsTo(toScrollView, .top, 0)
.setConstraints(.leading, 0)
.setConstraints(.trailing, 0)
.setConstraints(.bottom, 0)
let widthConstraint = widthAnchor.constraint(equalTo: toScrollView.widthAnchor)
if direction == .horizontal {
widthConstraint.priority = UILayoutPriority(250.0)
}
widthConstraint.isActive = true
let heigthConstraint = heightAnchor.constraint(equalTo: toScrollView.heightAnchor)
if direction == .vertical {
heigthConstraint.priority = UILayoutPriority(250.0)
}
heigthConstraint.isActive = true
}
}
#endif

View File

@@ -0,0 +1,172 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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
#if os(iOS) || os(macOS)
import UIKit
import QuartzCore
public enum ToastPosition {
case top
case down
}
public extension UIViewController {
var isVisible: Bool {
// http://stackoverflow.com/questions/2777438/how-to-tell-if-uiviewcontrollers-view-is-visible
return isViewLoaded && view.window != nil
}
var isLoaded: Bool {
return isVisible
}
var isModal: Bool {
if let index = navigationController?.viewControllers.firstIndex(of: self), index > 0 {
return false
} else if presentingViewController != nil {
return true
} else if navigationController?.presentingViewController?.presentedViewController == navigationController {
return true
} else if tabBarController?.presentingViewController is UITabBarController {
return true
} else {
return false
}
}
@objc func dismissSystemKeyboard(_ sender: UITapGestureRecognizer) {
if sender.state == .ended {
self.view.endEditing(true)
}
sender.cancelsTouchesInView = false
}
static var identifier: String {
return "id"+className
}
static var segueID: String {
return "idSegue"+className
}
static var className: String {
return String(describing: self)
}
static func instantiate<T: UIViewController>(storyBoard: String, identifier: String? = nil, bundle: Bundle? = Bundle(for: T.self)) -> T {
let storyboard = UIStoryboard(name: storyBoard, bundle: bundle)
var identf = T.identifier
if let id = identifier {
identf = id
}
let controller = storyboard.instantiateViewController(withIdentifier: identf) as! T
return controller
}
static func instatiate<T: UIViewController>(nibName: String, bundle: Bundle? = nil) -> T {
let controller = T(nibName: nibName, bundle: bundle)
controller.awakeFromNib()
controller.view.updateConstraints()
controller.view.layoutIfNeeded()
return controller
}
func present(viewControllerToPresent: UIViewController, completion: @escaping ()->()) {
CATransaction.begin()
self.present(viewControllerToPresent, animated: true) {
CATransaction.setCompletionBlock(completion)
}
CATransaction.commit()
}
func closeController(jumpToController: UIViewController? = nil, completion: @escaping ()->()){
if self.isModal {
self.dismiss(animated: true) {
completion()
}
return
}
guard let jump = jumpToController else {
self.navigationController?.popViewController(animated: true, {
completion()
})
return
}
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
self.navigationController?.popToViewController(jump, animated: true)
CATransaction.commit()
}
/// - Parameters:
/// - name: notification name.
/// - selector: selector to run with notified.
func addNotificationObserver(name: Notification.Name, selector: Selector) {
NotificationCenter.default.addObserver(self, selector: selector, name: name, object: nil)
}
///
/// - Parameter name: notification name.
func removeNotificationObserver(name: Notification.Name) {
NotificationCenter.default.removeObserver(self, name: name, object: nil)
}
/// Unassign as listener from all notifications.
func removeNotificationsObserver() {
NotificationCenter.default.removeObserver(self)
}
func show(toastWith message: String,
font: UIFont = UIFont.systemFont(ofSize: 12),
toastPosition: ToastPosition,
backgroundColor: UIColor = .black,
textColor: UIColor = .white,
duration: TimeInterval = 3.0) {
let yPostition = toastPosition == .top ? (UIDevice.hasNotch ? 44 : 24) : self.view.frame.size.height - 44 - 16//margin
let frame = CGRect(x: 30,
y: yPostition,
width: UIScreen.main.bounds.width - 60,
height: 44)
let toast = UILabel(frame: frame)
toast.backgroundColor = backgroundColor.withAlphaComponent(0.7)
toast.textColor = textColor
toast.textAlignment = .center;
toast.font = font
toast.text = message
toast.alpha = 1.0
toast.layer.cornerRadius = 10;
toast.clipsToBounds = true
self.view.addSubview(toast)
UIView.animate(withDuration: duration, delay: 4, options: .curveEaseInOut, animations: {
toast.alpha = 0.0
}, completion: {(isCompleted) in
toast.removeFromSuperview()
})
}
}
#endif

View File

@@ -0,0 +1,36 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
public extension URL {
var params: [String: String] {
get {
let urlComponents = URLComponents(url: self, resolvingAgainstBaseURL: false)
var items = [String: String]()
for item in urlComponents?.queryItems ?? [] {
items[item.name] = item.value ?? ""
}
return items
}
}
}

View File

@@ -0,0 +1,93 @@
//
// Copyright (c) 2018 Loverde Co.
//
// 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
@MainActor
public extension UserDefaults {
enum UserDefaultsKeys: String {
case isLoggedIn
case isFirstTimeOnApp
}
var isLoggedIn: Bool {
set {
set(newValue, forKey: UserDefaultsKeys.isLoggedIn.rawValue)
synchronize()
}
get { bool(forKey: UserDefaultsKeys.isLoggedIn.rawValue) }
}
var isFirstTimeOnApp: Bool {
set{
set(newValue, forKey: UserDefaultsKeys.isFirstTimeOnApp.rawValue)
synchronize()
}
get{ bool(forKey: UserDefaultsKeys.isFirstTimeOnApp.rawValue) }
}
/// Retrieves a Codable object from UserDefaults.
///
/// - Parameters:
/// - type: Class that conforms to the Codable protocol.
/// - key: Identifier of the object.
/// - decoder: Custom JSONDecoder instance. Defaults to `JSONDecoder()`.
/// - Returns: Codable object for key (if exists).
func object<T: Codable>(_ type: T.Type, with key: String, usingDecoder decoder: JSONDecoder = JSONDecoder()) -> T? {
guard let data = value(forKey: key) as? Data else { return nil }
return try? decoder.decode(type.self, from: data)
}
/// Allows storing of Codable objects to UserDefaults.
///
/// - Parameters:
/// - object: Codable object to store.
/// - key: Identifier of the object.
/// - encoder: Custom JSONEncoder instance. Defaults to `JSONEncoder()`.
func set<T: Codable>(object: T, forKey key: String, usingEncoder encoder: JSONEncoder = JSONEncoder()) {
let data = try? encoder.encode(object)
set(data, forKey: key)
synchronize()
}
func removeSavedObject(forKey: String) -> Bool {
if object(forKey: forKey) is String {
DispatchQueue.main.async {
self.removeObject(forKey: forKey)
self.synchronize()
}
return true
}
return false
}
func removeAllSaved() {
let domain = Bundle.main.bundleIdentifier ?? ""
UserDefaults.standard.removePersistentDomain(forName: domain)
UserDefaults.standard.synchronize()
}
func showEverything() -> [String: Any] {
return UserDefaults.standard.dictionaryRepresentation()
}
}

View File

@@ -0,0 +1,393 @@
//
// Copyright (c) 2024 Loverde Co.
//
// 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
public struct RIPEMD_160 {
private var MDbuf: (UInt32, UInt32, UInt32, UInt32, UInt32)
private var buffer: Data
private var count: Int64 // Total # of bytes processed.
private init() {
MDbuf = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0)
buffer = Data()
count = 0
}
private mutating func compress(_ X: UnsafePointer<UInt32>) {
// *** Helper functions (originally macros in rmd160.h) ***
/* ROL(x, n) cyclically rotates x over n bits to the left */
/* x must be of an unsigned 32 bits type and 0 <= n < 32. */
func ROL(_ x: UInt32, _ n: UInt32) -> UInt32 {
return (x << n) | ( x >> (32 - n))
}
/* the five basic functions F(), G() and H() */
func F(_ x: UInt32, _ y: UInt32, _ z: UInt32) -> UInt32 {
return x ^ y ^ z
}
func G(_ x: UInt32, _ y: UInt32, _ z: UInt32) -> UInt32 {
return (x & y) | (~x & z)
}
func H(_ x: UInt32, _ y: UInt32, _ z: UInt32) -> UInt32 {
return (x | ~y) ^ z
}
func I(_ x: UInt32, _ y: UInt32, _ z: UInt32) -> UInt32 {
return (x & z) | (y & ~z)
}
func J(_ x: UInt32, _ y: UInt32, _ z: UInt32) -> UInt32 {
return x ^ (y | ~z)
}
/* the ten basic operations FF() through III() */
func FF(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) {
a = a &+ F(b, c, d) &+ x
a = ROL(a, s) &+ e
c = ROL(c, 10)
}
func GG(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) {
a = a &+ G(b, c, d) &+ x &+ 0x5a827999
a = ROL(a, s) &+ e
c = ROL(c, 10)
}
func HH(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) {
a = a &+ H(b, c, d) &+ x &+ 0x6ed9eba1
a = ROL(a, s) &+ e
c = ROL(c, 10)
}
func II(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) {
a = a &+ I(b, c, d) &+ x &+ 0x8f1bbcdc
a = ROL(a, s) &+ e
c = ROL(c, 10)
}
func JJ(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) {
a = a &+ J(b, c, d) &+ x &+ 0xa953fd4e
a = ROL(a, s) &+ e
c = ROL(c, 10)
}
func FFF(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) {
a = a &+ F(b, c, d) &+ x
a = ROL(a, s) &+ e
c = ROL(c, 10)
}
func GGG(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) {
a = a &+ G(b, c, d) &+ x &+ 0x7a6d76e9
a = ROL(a, s) &+ e
c = ROL(c, 10)
}
func HHH(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) {
a = a &+ H(b, c, d) &+ x &+ 0x6d703ef3
a = ROL(a, s) &+ e
c = ROL(c, 10)
}
func III(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) {
a = a &+ I(b, c, d) &+ x &+ 0x5c4dd124
a = ROL(a, s) &+ e
c = ROL(c, 10)
}
func JJJ(_ a: inout UInt32, _ b: UInt32, _ c: inout UInt32, _ d: UInt32, _ e: UInt32, _ x: UInt32, _ s: UInt32) {
a = a &+ J(b, c, d) &+ x &+ 0x50a28be6
a = ROL(a, s) &+ e
c = ROL(c, 10)
}
// *** The function starts here ***
var (aa, bb, cc, dd, ee) = MDbuf
var (aaa, bbb, ccc, ddd, eee) = MDbuf
/* round 1 */
FF(&aa, bb, &cc, dd, ee, X[ 0], 11)
FF(&ee, aa, &bb, cc, dd, X[ 1], 14)
FF(&dd, ee, &aa, bb, cc, X[ 2], 15)
FF(&cc, dd, &ee, aa, bb, X[ 3], 12)
FF(&bb, cc, &dd, ee, aa, X[ 4], 5)
FF(&aa, bb, &cc, dd, ee, X[ 5], 8)
FF(&ee, aa, &bb, cc, dd, X[ 6], 7)
FF(&dd, ee, &aa, bb, cc, X[ 7], 9)
FF(&cc, dd, &ee, aa, bb, X[ 8], 11)
FF(&bb, cc, &dd, ee, aa, X[ 9], 13)
FF(&aa, bb, &cc, dd, ee, X[10], 14)
FF(&ee, aa, &bb, cc, dd, X[11], 15)
FF(&dd, ee, &aa, bb, cc, X[12], 6)
FF(&cc, dd, &ee, aa, bb, X[13], 7)
FF(&bb, cc, &dd, ee, aa, X[14], 9)
FF(&aa, bb, &cc, dd, ee, X[15], 8)
/* round 2 */
GG(&ee, aa, &bb, cc, dd, X[ 7], 7)
GG(&dd, ee, &aa, bb, cc, X[ 4], 6)
GG(&cc, dd, &ee, aa, bb, X[13], 8)
GG(&bb, cc, &dd, ee, aa, X[ 1], 13)
GG(&aa, bb, &cc, dd, ee, X[10], 11)
GG(&ee, aa, &bb, cc, dd, X[ 6], 9)
GG(&dd, ee, &aa, bb, cc, X[15], 7)
GG(&cc, dd, &ee, aa, bb, X[ 3], 15)
GG(&bb, cc, &dd, ee, aa, X[12], 7)
GG(&aa, bb, &cc, dd, ee, X[ 0], 12)
GG(&ee, aa, &bb, cc, dd, X[ 9], 15)
GG(&dd, ee, &aa, bb, cc, X[ 5], 9)
GG(&cc, dd, &ee, aa, bb, X[ 2], 11)
GG(&bb, cc, &dd, ee, aa, X[14], 7)
GG(&aa, bb, &cc, dd, ee, X[11], 13)
GG(&ee, aa, &bb, cc, dd, X[ 8], 12)
/* round 3 */
HH(&dd, ee, &aa, bb, cc, X[ 3], 11)
HH(&cc, dd, &ee, aa, bb, X[10], 13)
HH(&bb, cc, &dd, ee, aa, X[14], 6)
HH(&aa, bb, &cc, dd, ee, X[ 4], 7)
HH(&ee, aa, &bb, cc, dd, X[ 9], 14)
HH(&dd, ee, &aa, bb, cc, X[15], 9)
HH(&cc, dd, &ee, aa, bb, X[ 8], 13)
HH(&bb, cc, &dd, ee, aa, X[ 1], 15)
HH(&aa, bb, &cc, dd, ee, X[ 2], 14)
HH(&ee, aa, &bb, cc, dd, X[ 7], 8)
HH(&dd, ee, &aa, bb, cc, X[ 0], 13)
HH(&cc, dd, &ee, aa, bb, X[ 6], 6)
HH(&bb, cc, &dd, ee, aa, X[13], 5)
HH(&aa, bb, &cc, dd, ee, X[11], 12)
HH(&ee, aa, &bb, cc, dd, X[ 5], 7)
HH(&dd, ee, &aa, bb, cc, X[12], 5)
/* round 4 */
II(&cc, dd, &ee, aa, bb, X[ 1], 11)
II(&bb, cc, &dd, ee, aa, X[ 9], 12)
II(&aa, bb, &cc, dd, ee, X[11], 14)
II(&ee, aa, &bb, cc, dd, X[10], 15)
II(&dd, ee, &aa, bb, cc, X[ 0], 14)
II(&cc, dd, &ee, aa, bb, X[ 8], 15)
II(&bb, cc, &dd, ee, aa, X[12], 9)
II(&aa, bb, &cc, dd, ee, X[ 4], 8)
II(&ee, aa, &bb, cc, dd, X[13], 9)
II(&dd, ee, &aa, bb, cc, X[ 3], 14)
II(&cc, dd, &ee, aa, bb, X[ 7], 5)
II(&bb, cc, &dd, ee, aa, X[15], 6)
II(&aa, bb, &cc, dd, ee, X[14], 8)
II(&ee, aa, &bb, cc, dd, X[ 5], 6)
II(&dd, ee, &aa, bb, cc, X[ 6], 5)
II(&cc, dd, &ee, aa, bb, X[ 2], 12)
/* round 5 */
JJ(&bb, cc, &dd, ee, aa, X[ 4], 9)
JJ(&aa, bb, &cc, dd, ee, X[ 0], 15)
JJ(&ee, aa, &bb, cc, dd, X[ 5], 5)
JJ(&dd, ee, &aa, bb, cc, X[ 9], 11)
JJ(&cc, dd, &ee, aa, bb, X[ 7], 6)
JJ(&bb, cc, &dd, ee, aa, X[12], 8)
JJ(&aa, bb, &cc, dd, ee, X[ 2], 13)
JJ(&ee, aa, &bb, cc, dd, X[10], 12)
JJ(&dd, ee, &aa, bb, cc, X[14], 5)
JJ(&cc, dd, &ee, aa, bb, X[ 1], 12)
JJ(&bb, cc, &dd, ee, aa, X[ 3], 13)
JJ(&aa, bb, &cc, dd, ee, X[ 8], 14)
JJ(&ee, aa, &bb, cc, dd, X[11], 11)
JJ(&dd, ee, &aa, bb, cc, X[ 6], 8)
JJ(&cc, dd, &ee, aa, bb, X[15], 5)
JJ(&bb, cc, &dd, ee, aa, X[13], 6)
/* parallel round 1 */
JJJ(&aaa, bbb, &ccc, ddd, eee, X[ 5], 8)
JJJ(&eee, aaa, &bbb, ccc, ddd, X[14], 9)
JJJ(&ddd, eee, &aaa, bbb, ccc, X[ 7], 9)
JJJ(&ccc, ddd, &eee, aaa, bbb, X[ 0], 11)
JJJ(&bbb, ccc, &ddd, eee, aaa, X[ 9], 13)
JJJ(&aaa, bbb, &ccc, ddd, eee, X[ 2], 15)
JJJ(&eee, aaa, &bbb, ccc, ddd, X[11], 15)
JJJ(&ddd, eee, &aaa, bbb, ccc, X[ 4], 5)
JJJ(&ccc, ddd, &eee, aaa, bbb, X[13], 7)
JJJ(&bbb, ccc, &ddd, eee, aaa, X[ 6], 7)
JJJ(&aaa, bbb, &ccc, ddd, eee, X[15], 8)
JJJ(&eee, aaa, &bbb, ccc, ddd, X[ 8], 11)
JJJ(&ddd, eee, &aaa, bbb, ccc, X[ 1], 14)
JJJ(&ccc, ddd, &eee, aaa, bbb, X[10], 14)
JJJ(&bbb, ccc, &ddd, eee, aaa, X[ 3], 12)
JJJ(&aaa, bbb, &ccc, ddd, eee, X[12], 6)
/* parallel round 2 */
III(&eee, aaa, &bbb, ccc, ddd, X[ 6], 9)
III(&ddd, eee, &aaa, bbb, ccc, X[11], 13)
III(&ccc, ddd, &eee, aaa, bbb, X[ 3], 15)
III(&bbb, ccc, &ddd, eee, aaa, X[ 7], 7)
III(&aaa, bbb, &ccc, ddd, eee, X[ 0], 12)
III(&eee, aaa, &bbb, ccc, ddd, X[13], 8)
III(&ddd, eee, &aaa, bbb, ccc, X[ 5], 9)
III(&ccc, ddd, &eee, aaa, bbb, X[10], 11)
III(&bbb, ccc, &ddd, eee, aaa, X[14], 7)
III(&aaa, bbb, &ccc, ddd, eee, X[15], 7)
III(&eee, aaa, &bbb, ccc, ddd, X[ 8], 12)
III(&ddd, eee, &aaa, bbb, ccc, X[12], 7)
III(&ccc, ddd, &eee, aaa, bbb, X[ 4], 6)
III(&bbb, ccc, &ddd, eee, aaa, X[ 9], 15)
III(&aaa, bbb, &ccc, ddd, eee, X[ 1], 13)
III(&eee, aaa, &bbb, ccc, ddd, X[ 2], 11)
/* parallel round 3 */
HHH(&ddd, eee, &aaa, bbb, ccc, X[15], 9)
HHH(&ccc, ddd, &eee, aaa, bbb, X[ 5], 7)
HHH(&bbb, ccc, &ddd, eee, aaa, X[ 1], 15)
HHH(&aaa, bbb, &ccc, ddd, eee, X[ 3], 11)
HHH(&eee, aaa, &bbb, ccc, ddd, X[ 7], 8)
HHH(&ddd, eee, &aaa, bbb, ccc, X[14], 6)
HHH(&ccc, ddd, &eee, aaa, bbb, X[ 6], 6)
HHH(&bbb, ccc, &ddd, eee, aaa, X[ 9], 14)
HHH(&aaa, bbb, &ccc, ddd, eee, X[11], 12)
HHH(&eee, aaa, &bbb, ccc, ddd, X[ 8], 13)
HHH(&ddd, eee, &aaa, bbb, ccc, X[12], 5)
HHH(&ccc, ddd, &eee, aaa, bbb, X[ 2], 14)
HHH(&bbb, ccc, &ddd, eee, aaa, X[10], 13)
HHH(&aaa, bbb, &ccc, ddd, eee, X[ 0], 13)
HHH(&eee, aaa, &bbb, ccc, ddd, X[ 4], 7)
HHH(&ddd, eee, &aaa, bbb, ccc, X[13], 5)
/* parallel round 4 */
GGG(&ccc, ddd, &eee, aaa, bbb, X[ 8], 15)
GGG(&bbb, ccc, &ddd, eee, aaa, X[ 6], 5)
GGG(&aaa, bbb, &ccc, ddd, eee, X[ 4], 8)
GGG(&eee, aaa, &bbb, ccc, ddd, X[ 1], 11)
GGG(&ddd, eee, &aaa, bbb, ccc, X[ 3], 14)
GGG(&ccc, ddd, &eee, aaa, bbb, X[11], 14)
GGG(&bbb, ccc, &ddd, eee, aaa, X[15], 6)
GGG(&aaa, bbb, &ccc, ddd, eee, X[ 0], 14)
GGG(&eee, aaa, &bbb, ccc, ddd, X[ 5], 6)
GGG(&ddd, eee, &aaa, bbb, ccc, X[12], 9)
GGG(&ccc, ddd, &eee, aaa, bbb, X[ 2], 12)
GGG(&bbb, ccc, &ddd, eee, aaa, X[13], 9)
GGG(&aaa, bbb, &ccc, ddd, eee, X[ 9], 12)
GGG(&eee, aaa, &bbb, ccc, ddd, X[ 7], 5)
GGG(&ddd, eee, &aaa, bbb, ccc, X[10], 15)
GGG(&ccc, ddd, &eee, aaa, bbb, X[14], 8)
/* parallel round 5 */
FFF(&bbb, ccc, &ddd, eee, aaa, X[12] , 8)
FFF(&aaa, bbb, &ccc, ddd, eee, X[15] , 5)
FFF(&eee, aaa, &bbb, ccc, ddd, X[10] , 12)
FFF(&ddd, eee, &aaa, bbb, ccc, X[ 4] , 9)
FFF(&ccc, ddd, &eee, aaa, bbb, X[ 1] , 12)
FFF(&bbb, ccc, &ddd, eee, aaa, X[ 5] , 5)
FFF(&aaa, bbb, &ccc, ddd, eee, X[ 8] , 14)
FFF(&eee, aaa, &bbb, ccc, ddd, X[ 7] , 6)
FFF(&ddd, eee, &aaa, bbb, ccc, X[ 6] , 8)
FFF(&ccc, ddd, &eee, aaa, bbb, X[ 2] , 13)
FFF(&bbb, ccc, &ddd, eee, aaa, X[13] , 6)
FFF(&aaa, bbb, &ccc, ddd, eee, X[14] , 5)
FFF(&eee, aaa, &bbb, ccc, ddd, X[ 0] , 15)
FFF(&ddd, eee, &aaa, bbb, ccc, X[ 3] , 13)
FFF(&ccc, ddd, &eee, aaa, bbb, X[ 9] , 11)
FFF(&bbb, ccc, &ddd, eee, aaa, X[11] , 11)
/* combine results */
MDbuf = (MDbuf.1 &+ cc &+ ddd,
MDbuf.2 &+ dd &+ eee,
MDbuf.3 &+ ee &+ aaa,
MDbuf.4 &+ aa &+ bbb,
MDbuf.0 &+ bb &+ ccc)
}
mutating private func update(data: Data) {
data.withUnsafeBytes { (pointer) -> Void in
guard var ptr = pointer.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return }
var length = data.count
var X = [UInt32](repeating: 0, count: 16)
// Process remaining bytes from last call:
if buffer.count > 0 && buffer.count + length >= 64 {
let amount = 64 - buffer.count
buffer.append(ptr, count: amount)
buffer.withUnsafeBytes { _ = memcpy(&X, $0.baseAddress, 64) }
compress(X)
ptr += amount
length -= amount
}
// Process 64 byte chunks:
while length >= 64 {
memcpy(&X, ptr, 64)
compress(X)
ptr += 64
length -= 64
}
// Save remaining unprocessed bytes:
buffer = Data(bytes: ptr, count: length)
}
count += Int64(data.count)
}
mutating private func finalize() -> Data {
var X = [UInt32](repeating: 0, count: 16)
/* append the bit m_n == 1 */
buffer.append(0x80)
buffer.withUnsafeBytes { _ = memcpy(&X, $0.baseAddress, buffer.count) }
if (count & 63) > 55 {
/* length goes to next block */
compress(X)
X = [UInt32](repeating: 0, count: 16)
}
/* append length in bits */
let lswlen = UInt32(truncatingIfNeeded: count)
let mswlen = UInt32(UInt64(count) >> 32)
X[14] = lswlen << 3
X[15] = (lswlen >> 29) | (mswlen << 3)
compress(X)
var data = Data(count: 20)
data.withUnsafeMutableBytes { (pointer) -> Void in
let ptr = pointer.bindMemory(to: UInt32.self)
ptr[0] = MDbuf.0
ptr[1] = MDbuf.1
ptr[2] = MDbuf.2
ptr[3] = MDbuf.3
ptr[4] = MDbuf.4
}
buffer = Data()
return data
}
}
public extension RIPEMD_160 {
static func hash(_ message: Data) -> Data {
var md = RIPEMD_160()
md.update(data: message)
return md.finalize()
}
}

View File

@@ -0,0 +1,271 @@
//
// Copyright (c) 2020 Loverde Co.
//
// 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
import UIKit
#if os(iOS) || os(macOS)
@objc public protocol HUDAlertViewControllerDelegate {
@objc optional func didOpen(alert: HUDAlertViewController)
@objc optional func didClose(alert: HUDAlertViewController)
}
public enum HUDAlertActionType: Int {
case cancel = 0
case normal = 1
case destructive = 2
case discrete = 3
case green = 4
}
public class HUDAlertAction {
public typealias CompletionBlock = () -> Void
var title: String = ""
var tag: Int = 0
var type: HUDAlertActionType = .cancel
var completionBlock: CompletionBlock? = nil
public convenience init(title: String, type: HUDAlertActionType, _ completion: (() -> Void)? = nil) {
self.init()
//
self.title = title
self.type = type
self.completionBlock = completion
}
}
public class HUDAlertViewController: UIViewController {
fileprivate var viewController: UIViewController
lazy var actions: [HUDAlertAction] = []
let greenColor: UIColor = UIColor(hex: "609c70")
let redColor: UIColor = UIColor(hex: "a32a2e")
let blueColor: UIColor = UIColor(hex: "2a50a8")
let greyColor: UIColor = UIColor(hex: "a6a6a6")
public weak var delegate: HUDAlertViewControllerDelegate?
public var isLoadingAlert: Bool = true
public var customView: HUDAlertView {
return (view as? HUDAlertView) ?? HUDAlertView()
}
public init() {
self.viewController = UIViewController()
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required public init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func loadView() {
view = HUDAlertView()
}
public override func viewDidLoad() {
super.viewDidLoad()
if isLoaded {
printLog(title: "VIEW DIDLOAD", msg: "IS LOADED", prettyPrint: true)
}
}
public func setTitle(_ title: String = "", font: UIFont = UIFont.systemFont(ofSize: 16.0), color: UIColor = .black) {
self.customView.titleLabel.text = title
self.customView.titleLabel.font = font
self.customView.titleLabel.textColor = color
}
public func setDescription(_ desc: String = "", font: UIFont = .systemFont(ofSize: 14.0), color: UIColor = .black) {
self.customView.descLabel.text = desc
self.customView.descLabel.font = font
self.customView.descLabel.textColor = color
}
public func configureAlertWith(title: String,
description: String? = nil,
viewController: UIViewController? = nil,
actionButtons: [HUDAlertAction] = []) {
if let viewController {
self.viewController = viewController
} else if let topViewController = LCEssentials.getTopViewController(aboveBars: true) {
self.viewController = topViewController
} else {
fatalError("Ops! No UIViewController was found.")
}
if self.isLoadingAlert {
self.customView.rotatinProgress.progress = 0.8
self.customView.rotatinProgress.heightConstraint?.constant = 40
}else{
self.customView.rotatinProgress.progress = 0
self.customView.rotatinProgress.heightConstraint?.constant = 0
}
self.customView.containerView.cornerRadius = 8
self.customView.titleLabel.text = title
self.customView.descLabel.text = description
self.actions.removeAll()
self.customView.stackButtons.removeAllArrangedSubviews()
if !actionButtons.isEmpty {
for (index, element) in actionButtons.enumerated() {
lazy var button: UIButton = {
$0.isOpaque = true
$0.translatesAutoresizingMaskIntoConstraints = false
$0.setTitleForAllStates(element.title)
$0.tag = index
$0.isExclusiveTouch = true
$0.isUserInteractionEnabled = true
$0.cornerRadius = 5
$0.setHeight(size: 47.0)
$0.addTarget(self, action: #selector(self.actionButton(sender:)), for: .touchUpInside)
return $0
}(UIButton(type: .custom))
switch element.type {
case .cancel:
button.borderWidth = 1
button.borderColor = self.blueColor
button.backgroundColor = .white
button.setTitleColorForAllStates(self.blueColor)
case .destructive:
button.borderWidth = 0
button.borderColor = nil
button.backgroundColor = self.redColor
button.setTitleColorForAllStates(.white)
case .normal:
button.borderWidth = 0
button.borderColor = nil
button.backgroundColor = self.blueColor
button.setTitleColorForAllStates(.white)
case .discrete:
button.borderWidth = 0
button.borderColor = nil
button.backgroundColor = self.greyColor
button.setTitleColorForAllStates(.white)
case .green:
button.borderWidth = 0
button.borderColor = nil
button.backgroundColor = self.greenColor
button.setTitleColorForAllStates(.white)
}
self.customView.stackButtons.isHidden = false
self.customView.stackButtons.spacing = 10
self.customView.stackButtons.heightConstraint?.isActive = false
self.customView.stackButtons.addArrangedSubview(button)
self.actions.append(element)
}
} else {
self.actions.removeAll()
self.customView.stackButtons.removeAllArrangedSubviews()
self.customView.stackButtons.spacing = 0
self.customView.stackButtons.heightConstraint?.isActive = true
self.customView.stackButtons.heightConstraint?.constant = 0
self.customView.stackButtons.isHidden = true
}
}
@objc private func actionButton(sender: UIButton) {
self.actions[exist: sender.tag]?.completionBlock?()
self.closeAlert()
}
public func showAlert(){
if self.presentingViewController != nil {
self.closeAlert()
LCEssentials.backgroundThread(delay: 0.2, completion: {
self.showAlert()
})
return
}
self.modalTransitionStyle = .crossDissolve
self.modalPresentationStyle = .overFullScreen
self.viewController.present(self, animated: false, completion: {
self.animateIn {
self.delegate?.didOpen?(alert: self)
}
})
}
public func closeAlert(){
animateOut {
self.dismiss(animated: false) {
self.delegate?.didClose?(alert: self)
}
}
}
private func animateIn(completion: @escaping() -> Void) {
UIView.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .allowAnimatedContent, .curveEaseOut], animations: {
self.view.alpha = 1.0
}) { (completed) in
self.performSpringAnimationIn(for: self.customView.containerView) {
completion()
}
}
}
private func animateOut(completion: @escaping() -> Void) {
self.performSpringAnimationOut(for: self.customView.containerView) {
UIView.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .allowAnimatedContent, .curveEaseIn], animations: {
self.view.alpha = 0.0
}) { (completed) in
completion()
}
}
}
private func performSpringAnimationIn(for view: UIView, completion: @escaping() -> Void) {
view.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
//
UIView.animate(withDuration: 0.15, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseIn, animations: {
view.transform = CGAffineTransform(scaleX: 1, y: 1)
view.alpha = 1.0
}) { (completed) in
completion()
}
}
private func performSpringAnimationOut(for view: UIView, completion: @escaping() -> Void) {
UIView.animate(withDuration: 0.15, animations: {
view.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
view.alpha = 0.0
}) { (completed) in
completion()
}
}
}
#endif

View File

@@ -0,0 +1,158 @@
//
// Copyright (c) 2022 Loverde Co.
//
// 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.
// MARK: - Framework headers
import UIKit
// MARK: - Protocols
// MARK: - Interface Headers
// MARK: - Local Defines / ENUMS
// MARK: - Class
public final class HUDAlertView: UIView {
// MARK: - Private properties
// MARK: - Internal properties
lazy var containerView: UIView = {
$0.isOpaque = true
$0.backgroundColor = .white
$0.translatesAutoresizingMaskIntoConstraints = false
return $0
}(UIView())
let rotatinProgress: RotatingCircularGradientProgressBar = {
$0.layer.masksToBounds = true
$0.color = .gray
$0.gradientColor = .gray
$0.ringWidth = 2
$0.progress = 0
return $0
}(RotatingCircularGradientProgressBar())
lazy var titleLabel: UILabel = {
$0.font = UIFont.systemFont(ofSize: 22.0)
$0.textColor = .black
$0.backgroundColor = UIColor.clear
$0.numberOfLines = 0
$0.textAlignment = .center
$0.isOpaque = true
$0.translatesAutoresizingMaskIntoConstraints = false
return $0
}(UILabel())
lazy var descLabel: UILabel = {
$0.font = UIFont.systemFont(ofSize: 16.0)
$0.textColor = .black
$0.backgroundColor = UIColor.clear
$0.numberOfLines = 0
$0.textAlignment = .center
$0.isOpaque = true
$0.translatesAutoresizingMaskIntoConstraints = false
return $0
}(UILabel())
lazy var stackButtons: UIStackView = {
$0.axis = .vertical
$0.spacing = 10.0
$0.distribution = .fill
$0.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
$0.isLayoutMarginsRelativeArrangement = true
$0.translatesAutoresizingMaskIntoConstraints = false
return $0
}(UIStackView())
// MARK: - Public properties
// MARK: - Initializers
public init() {
super.init(frame: .zero)
insertBlurView(style: .dark, color: .clear, alpha: 0.8)
addComponentsAndConstraints()
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Super Class Overrides
// MARK: - Private methods
fileprivate func addComponentsAndConstraints() {
// MARK: - Add Subviews
containerView.addSubviews([rotatinProgress, titleLabel, descLabel, stackButtons])
addSubviews([containerView])
// MARK: - Add Constraints
rotatinProgress
.setConstraintsTo(containerView, .top, 10)
.setConstraints(.centerX, 0)
rotatinProgress.setWidth(size: 40)
rotatinProgress.setHeight(size: 40)
titleLabel
.setConstraintsTo(rotatinProgress, .topToBottom, 8)
.setConstraintsTo(containerView, .leading, 10)
.setConstraints(.trailing, -10)
descLabel
.setConstraintsTo(titleLabel, .topToBottom, 8)
.setConstraintsTo(containerView, .leading, 10)
.setConstraints(.trailing, -10)
stackButtons
.setConstraintsTo(descLabel, .topToBottom, 8)
.setConstraintsTo(containerView, .leading, 10)
.setConstraints(.leading, 10)
.setConstraints(.trailing, -10)
.setConstraints(.bottom, -8)
containerView
.setConstraintsTo(self, .topToTopGreaterThanOrEqualTo, 20, true)
.setConstraints(.leading, 40)
.setConstraints(.trailing, -40)
.setConstraints(.centerX, 0)
.setConstraints(.centerY, 0)
}
// MARK: - Internal methods
}
// MARK: - Extensions

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View File

@@ -0,0 +1,211 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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.
#if os(iOS) || os(macOS)
import UIKit
import AVFoundation
import Photos
public protocol ImagePickerControllerDelegate: AnyObject {
func imagePicker(didSelect image: UIImage?)
}
public class ImagePickerController: UIViewController, UINavigationControllerDelegate {
private var isAlertOpen: Bool = false
private var imagePickerController: UIImagePickerController = UIImagePickerController()
public weak var delegate: ImagePickerControllerDelegate?
public var isEditable: Bool = false
public init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
self.imagePickerController.delegate = self
self.imagePickerController.allowsEditing = isEditable
self.imagePickerController.mediaTypes = ["public.image", "public.movie"]
}
public func openImagePicker(){
var cameraPerm: Bool = false
var albumPerm: Bool = false
//Camera
if AVCaptureDevice.authorizationStatus(for: AVMediaType.video) == AVAuthorizationStatus.authorized {
// Already Authorized
cameraPerm = true
} else {
cameraPerm = false
}
//Photos
let photos = PHPhotoLibrary.authorizationStatus()
if photos == .authorized {
albumPerm = true
}else if photos == .notDetermined || photos == .denied || photos == .restricted {
albumPerm = false
}
DispatchQueue.main.async {
if let presentedController = (self.delegate as? UIViewController)?.presentedViewController, presentedController == self {
self.openAlerts(forCamera: cameraPerm, forAlbum: albumPerm)
}else{
(self.delegate as? UIViewController)?.present(self, animated:false, completion: {
self.openAlerts(forCamera: cameraPerm, forAlbum: albumPerm)
})
}
}
}
private func openAlerts(forCamera:Bool = true, forAlbum:Bool = true){
let alert = UIAlertController(title: "Choose an option", message: nil, preferredStyle: .actionSheet)
if forCamera {
alert.addAction(UIAlertAction(title: "Camera", style: .default, handler: { _ in
self.openCameraDevice()
}))
}else{
alert.addAction(UIAlertAction(title: "Allow camera permission", style: .default, handler: { _ in
self.openAppSettingsCamera()
}))
}
if forAlbum {
alert.addAction(UIAlertAction(title: "Camera roll", style: .default, handler: { _ in
self.openAlbumDevice()
}))
}else{
alert.addAction(UIAlertAction(title: "Allow camera roll permission", style: .default, handler: { _ in
self.openAppSettingsPhoto()
}))
}
alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: { _ in
self.dismiss(animated: false, completion: nil)
}))
self.isAlertOpen = true
DispatchQueue.main.async {
self.modalPresentationStyle = .fullScreen
self.present(alert, animated: true, completion: nil)
}
}
private func openCameraDevice(){
if(UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera)){
self.imagePickerController.sourceType = UIImagePickerController.SourceType.camera
self.modalPresentationStyle = .fullScreen
self.present(self.imagePickerController, animated: true, completion: nil)
}else{
let alert = UIAlertController(title: "Warning", message: "You don't have camera", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.modalPresentationStyle = .fullScreen
self.present(alert, animated: true, completion: nil)
}
}
private func openAlbumDevice(){
self.imagePickerController.sourceType = UIImagePickerController.SourceType.photoLibrary
self.modalPresentationStyle = .fullScreen
self.present(self.imagePickerController, animated: true, completion: nil)
}
private func openAppSettingsPhoto(){
PHPhotoLibrary.requestAuthorization({status in
if status == .authorized{
if self.isAlertOpen {
self.isAlertOpen = false
self.openImagePicker()
}
}else{
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
})
}
else {
UIApplication.shared.openURL(settingsUrl)
}
}
}
})
}
private func openAppSettingsCamera(){
AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { (granted: Bool) -> Void in
if granted == true {
if self.isAlertOpen {
self.isAlertOpen = false
self.openImagePicker()
}
} else {
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
})
}
else {
UIApplication.shared.openURL(settingsUrl)
}
}
}
})
}
}
//MARK: - UIImagePicker Delegate
extension ImagePickerController: UIImagePickerControllerDelegate {
@objc public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.delegate?.imagePicker(didSelect: nil)
picker.dismiss(animated: true, completion: {
self.dismiss(animated: false, completion: nil)
})
}
@objc public func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
self.delegate?.imagePicker(didSelect: image)
}else if let image = info[ UIImagePickerController.InfoKey.originalImage] as? UIImage {
self.delegate?.imagePicker(didSelect: image)
}
picker.dismiss(animated: true, completion: {
self.dismiss(animated: false, completion: nil)
})
}
}
#endif

View File

@@ -0,0 +1,205 @@
//
// Copyright (c) 2023 Loverde Co.
//
// 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
#if os(iOS) || os(macOS)
@objc public protocol ImageZoomControllerDelegate {
@objc optional func imageZoomController(controller: ImageZoomController, didZoom image: UIImage?)
@objc optional func imageZoomController(controller: ImageZoomController, didClose image: UIImage?)
}
public class ImageZoomController: UIViewController {
fileprivate lazy var blackView: UIView = {
$0.isOpaque = true
$0.translatesAutoresizingMaskIntoConstraints = false
$0.backgroundColor = UIColor.black
$0.alpha = 0.8
return $0
}(UIView())
fileprivate lazy var scrollView: UIScrollView = {
$0.isOpaque = true
$0.translatesAutoresizingMaskIntoConstraints = false
$0.delegate = self
return $0
}(UIScrollView())
fileprivate lazy var closeButton: UIButton = {
$0.isOpaque = true
$0.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 13.0, *) {
$0.setImage(UIImage(systemName: "xmark"), for: .normal)
$0.tintColor = UIColor.white
}
$0.addTarget(self, action: #selector(self.close), for: .touchUpInside)
return $0
}(UIButton(type: .custom))
lazy var imageView: UIImageView = {
$0.isOpaque = true
$0.translatesAutoresizingMaskIntoConstraints = false
$0.isUserInteractionEnabled = true
return $0
}(UIImageView())
public var minimumZoomScale: CGFloat = 1.0
public var maximumZoomScale: CGFloat = 6.0
public var addGestureToDismiss: Bool = true
public weak var delegate: ImageZoomControllerDelegate?
private var minimumVelocityToHide: CGFloat = 1500
private var minimumScreenRatioToHide: CGFloat = 0.5
private var animationDuration: TimeInterval = 0.2
public init(_ withImage: UIImage) {
super.init(nibName: nil, bundle: nil)
self.addComponentsAndConstraints()
self.imageView.image = withImage
self.imageView.addAspectRatioConstraint()
self.scrollView.minimumZoomScale = self.minimumZoomScale
self.scrollView.maximumZoomScale = self.maximumZoomScale
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func viewDidLoad() {
super.viewDidLoad()
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if addGestureToDismiss {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(onPan(_:)))
view.addGestureRecognizer(panGesture)
}
}
fileprivate func addComponentsAndConstraints() {
scrollView.addSubview(imageView, translatesAutoresizingMaskIntoConstraints: false)
view.addSubviews([blackView, scrollView, closeButton])
blackView
.setConstraintsTo(view, .all, 0, true)
scrollView
.setConstraintsTo(view, .all, 0, true)
imageView.setHeight(min: 200)
imageView
.setConstraintsTo(scrollView, .centerX, 0)
.setConstraints(.centerY, 0)
closeButton
.setConstraintsTo(view, .leading, 20, true)
.setConstraints(.top, 0)
.setWidth(size: 50.0)
.setHeight(size: 50.0)
}
public func present(completion: (()->())? = nil) {
guard let viewController = LCEssentials.getTopViewController(aboveBars: true) else {
fatalError("Ops! Look like it doesnt have a ViewController")
}
self.modalTransitionStyle = .coverVertical
self.modalPresentationStyle = .overFullScreen
viewController.present(self, animated: true) {
completion?()
}
}
public func dismiss(completion: (()->())? = nil) {
self.dismiss(animated: true) {
completion?()
}
}
@objc private func close(){
delegate?.imageZoomController?(controller: self, didClose: self.imageView.image)
self.dismiss()
}
private func slideViewVerticallyTo(_ y: CGFloat) {
self.view.frame.origin = CGPoint(x: 0, y: y)
}
@objc private func onPan(_ panGesture: UIPanGestureRecognizer) {
switch panGesture.state {
case .began, .changed:
// If pan started or is ongoing then
// slide the view to follow the finger
let translation = panGesture.translation(in: view)
let y = max(0, translation.y)
slideViewVerticallyTo(y)
case .ended:
// If pan ended, decide it we should close or reset the view
// based on the final position and the speed of the gesture
let translation = panGesture.translation(in: view)
let velocity = panGesture.velocity(in: view)
let closing = (translation.y > self.view.frame.size.height * minimumScreenRatioToHide) || (velocity.y > minimumVelocityToHide)
if closing {
UIView.animate(withDuration: animationDuration, animations: {
// If closing, animate to the bottom of the view
self.slideViewVerticallyTo(self.view.frame.size.height)
}, completion: { (isCompleted) in
if isCompleted {
// Dismiss the view when it dissapeared
self.dismiss(animated: false, completion: nil)
}
})
} else {
// If not closing, reset the view to the top
UIView.animate(withDuration: animationDuration, animations: {
self.slideViewVerticallyTo(0)
})
}
default:
// If gesture state is undefined, reset the view to the top
UIView.animate(withDuration: animationDuration, animations: {
self.slideViewVerticallyTo(0)
})
}
}
}
extension ImageZoomController: UIScrollViewDelegate {
public func scrollViewDidZoom(_ scrollView: UIScrollView) {
delegate?.imageZoomController?(controller: self, didZoom: self.imageView.image)
}
public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.imageView
}
}
#endif

View File

@@ -0,0 +1,439 @@
//
// Copyright (c) 2024 Loverde Co.
//
// 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.
// MARK: - Framework headers
import UIKit
// MARK: - Protocols
@objc
public protocol LCSnackBarViewDelegate {
@objc optional func snackbar(didStartExibition: LCSnackBarView)
@objc optional func snackbar(didTouchOn snackbar: LCSnackBarView)
@objc optional func snackbar(didEndExibition: LCSnackBarView)
}
// MARK: - Interface Headers
// MARK: - Local Defines / ENUMS
public enum LCSnackBarViewType {
case `default`, rounded
}
public enum LCSnackBarOrientation {
case top, bottom
}
public enum LCSnackBarTimer: CGFloat {
case infinity = 0
case minimum = 2
case medium = 5
case maximum = 10
}
// MARK: - Class
/// LCSnackBarView is a simple SnackBar that you can display notifications in app to improve your app comunication
///
/// Usage example:
///
///```swift
///let notification = LCSnackBarView()
///notification
/// .configure(text: "Hello World!")
/// .present()
///```
///You can set delegate to interact with it
///
///```swift
///let notification = LCSnackBarView(delegate: self)
///notification
/// .configure(text: "Hello World!")
/// .present()
///
///public func snackbar(didStartExibition: LCSnackBarView){}
///public func snackbar(didTouchOn snackbar: LCSnackBarView){}
///public func snackbar(didEndExibition: LCSnackBarView){}
///```
public final class LCSnackBarView: UIView {
// MARK: - Private properties
private lazy var contentView: UIView = {
$0.backgroundColor = .white
$0.translatesAutoresizingMaskIntoConstraints = false
$0.isOpaque = true
return $0
}(UIView())
private lazy var descriptionLabel: UILabel = {
$0.font = .systemFont(ofSize: 12, weight: .regular)
$0.text = nil
$0.textColor = .black
$0.backgroundColor = UIColor.clear
$0.numberOfLines = 0
$0.lineBreakMode = .byWordWrapping
$0.textAlignment = .center
$0.isOpaque = true
$0.translatesAutoresizingMaskIntoConstraints = false
return $0
}(UILabel())
private var _style: LCSnackBarViewType
private var originPositionX: CGFloat = 0.0
private var originPositionY: CGFloat = 0.0
private var _orientation: LCSnackBarOrientation
private var _timer: LCSnackBarTimer = .minimum
private var _radius: CGFloat = 4.0
private var spacing: CGFloat = 20.0
private var _width: CGFloat = .zero
private var _height: CGFloat = .zero
private lazy var systemKeyboardVisible = false
private lazy var isOpen = false
// MARK: - Internal properties
// MARK: - Public properties
public weak var delegate: LCSnackBarViewDelegate?
// MARK: - Initializers
public init(
style: LCSnackBarViewType = .default,
orientation: LCSnackBarOrientation = .top,
delegate: LCSnackBarViewDelegate? = nil
) {
self._style = style
self._orientation = orientation
self.delegate = delegate
super.init(frame: .zero)
setupDefaultLayout()
addComponentsAndConstraints()
setupGestureRecognizer()
setKeyboardObserver()
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Super Class Overrides
public override func layoutSubviews() {
super.layoutSubviews()
}
deinit {
NotificationCenter.default.removeObserver(self, name: nil, object: nil)
}
}
// MARK: - Extensions
public extension LCSnackBarView {
// MARK: - Private methods
private func setupDefaultLayout() {
backgroundColor = .white
contentView.backgroundColor = .white
clipsToBounds = true
}
private func setupGestureRecognizer() {
let gesture = UITapGestureRecognizer(target: self, action: #selector(onTapGestureAction))
gesture.numberOfTapsRequired = 1
gesture.cancelsTouchesInView = false
addGestureRecognizer(gesture)
}
private func setKeyboardObserver() {
// Show
NotificationCenter
.default
.addObserver(
self,
selector: #selector(self.keyboardWillShow(_:)),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
// Hidde
NotificationCenter
.default
.addObserver(
self,
selector: #selector(self.keyboardWillHide(_:)),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
}
@objc private func keyboardWillShow(_ notification: Notification?) -> Void {
if let info = notification?.userInfo {
systemKeyboardVisible = true
//
let curveUserInfoKey = UIResponder.keyboardAnimationCurveUserInfoKey
let durationUserInfoKey = UIResponder.keyboardAnimationDurationUserInfoKey
let frameEndUserInfoKey = UIResponder.keyboardFrameEndUserInfoKey
//
var animationCurve: UIView.AnimationOptions = .curveEaseOut
var animationDuration: TimeInterval = 0.25
var height:CGFloat = 0.0
// Getting keyboard animation.
if let curve = info[curveUserInfoKey] as? UIView.AnimationOptions {
animationCurve = curve
}
// Getting keyboard animation duration
if let duration = info[durationUserInfoKey] as? TimeInterval {
animationDuration = duration
}
// Getting UIKeyboardSize.
if let kbFrame = info[frameEndUserInfoKey] as? CGRect {
height = kbFrame.size.height
}
DispatchQueue.main.async { [weak self] in
UIView.animate(withDuration: animationDuration,
delay: 0,
options: animationCurve,
animations: {
self?.frame.origin.y += height
})
}
}
}
@objc private func keyboardWillHide(_ notification: Notification?) -> Void {
DispatchQueue.main.async { [weak self] in
self?.systemKeyboardVisible = false
// keyboard is hidded
}
}
private func updateStyle() {
switch _style {
case .rounded:
_width = UIScreen.main.bounds.width - spacing
cornerRadius = _radius
originPositionX = 10
default:
_width = UIScreen.main.bounds.width
cornerRadius = 0
originPositionX = 0
}
}
private func positioningView(_ view: UIView) {
view
.addSubview(self,
translatesAutoresizingMaskIntoConstraints: true)
switch _orientation {
case .bottom:
var bottomNotch = UIDevice.bottomNotch
if _style != .rounded {
bottomNotch = (1.25 * bottomNotch)
contentView.bottomConstraint?.isActive = false
}
_height = (descriptionLabel.lineNumbers().cgFloat * descriptionLabel.font.pointSize) + spacing + bottomNotch
originPositionY = UIScreen.main.bounds.height
default:
var topNotch = UIDevice.topNotch
if _style != .rounded {
topNotch = (1.25 * topNotch)
contentView.topConstraint?.isActive = false
}
_height = (descriptionLabel.lineNumbers().cgFloat * descriptionLabel.font.pointSize) + spacing + topNotch
originPositionY = -_height
}
frame = CGRect(x: originPositionX,
y: originPositionY,
width: _width,
height: _height)
}
private func showSnackBar(controller: UIViewController, completion: @escaping (() -> Void)) {
if isOpen {
closeSnackBar(controller: controller) {
self.showSnackBar(controller: controller, completion: completion)
}
return
}
isHidden = true
updateStyle()
positioningView(controller.view)
let distance = CGFloat(_style == .rounded ? (_orientation == .top ? UIDevice.topNotch : UIDevice.bottomNotch) : 0)
layoutIfNeeded()
UIView.animate(withDuration: 0.6,
delay: 0.6,
options: .curveEaseInOut) { [weak self] in
self?.layoutIfNeeded()
self?.isHidden = false
if self?._orientation == .top {
self?.frame.origin.y += (self?._height ?? 0) + distance
} else {
self?.frame.origin.y -= (self?._height ?? 0) + distance
}
} completion: { finished in
self.layoutIfNeeded()
completion()
self.isOpen = true
self.delegate?.snackbar?(didStartExibition: self)
guard self._timer != .infinity else { return }
LCEssentials.delay(milliseconds: self._timer.rawValue) {
self.closeSnackBar(controller: controller, completion: {})
}
}
}
private func closeSnackBar(controller: UIViewController, completion: @escaping (() -> Void)) {
let distance = CGFloat(_style == .rounded ? (_orientation == .top ? UIDevice.topNotch : UIDevice.bottomNotch) : 0)
layoutIfNeeded()
UIView.animate(withDuration: 0.6,
delay: 0.6,
options: .curveEaseInOut) { [weak self] in
self?.layoutIfNeeded()
if self?._orientation == .top {
self?.frame.origin.y -= (self?._height ?? 0) + distance
} else {
self?.frame.origin.y += (self?._height ?? 0) + distance
}
} completion: { finished in
self.layoutIfNeeded()
self.delegate?.snackbar?(didEndExibition: self)
self.isOpen = false
self.removeFromSuperview()
completion()
}
}
@objc
private func onTapGestureAction(_ : UITapGestureRecognizer) {
self.delegate?.snackbar?(didTouchOn: self)
if let controller = LCEssentials.getTopViewController(aboveBars: true),
_timer == .infinity {
self.closeSnackBar(controller: controller, completion: {})
}
}
private func addComponentsAndConstraints() {
// MARK: - Add Subviews
contentView.addSubviews([descriptionLabel])
addSubviews([contentView])
// MARK: - Add Constraints
contentView
.setConstraintsTo(self, .top, 10)
.setConstraints(.leading, 10)
.setConstraints(.trailing, -10)
.setConstraints(.bottom, -10)
descriptionLabel
.setConstraintsTo(contentView, .top, 0)
.setConstraints(.leading, 0)
.setConstraints(.trailing, 0)
.setConstraints(.bottom, 0)
}
}
public extension LCSnackBarView {
// MARK: - Public methods
@discardableResult
func configure(text: String) -> Self {
descriptionLabel.text = text
return self
}
@discardableResult
func configure(textColor: UIColor) -> Self {
descriptionLabel.textColor = textColor
return self
}
@discardableResult
func configure(textFont: UIFont, alignment: NSTextAlignment = .center) -> Self {
descriptionLabel.font = textFont
descriptionLabel.textAlignment = alignment
return self
}
@discardableResult
func configure(backgroundColor: UIColor) -> Self {
self.backgroundColor = backgroundColor
contentView.backgroundColor = backgroundColor
return self
}
@discardableResult
func configure(exibition timer: LCSnackBarTimer) -> Self {
_timer = timer
return self
}
@discardableResult
func configure(imageIconBefore icon: UIImageView, withTintColor: UIColor? = nil) -> Self {
icon.setHeight(size: 24)
icon.setWidth(size: 24)
icon.contentMode = .scaleAspectFit
descriptionLabel.leadingConstraint?.constant = (icon.widthConstraint?.constant ?? 0) + 24
if let withTintColor {
icon.image = icon.image?.withRenderingMode(.alwaysTemplate).tintImage(color: withTintColor)
}
contentView
.addSubviews([icon])
icon
.setConstraintsTo(contentView, .leading, 10)
.setConstraintsTo(descriptionLabel, .centerY, 0)
return self
}
func present(completion: (()->())? = nil) {
if isOpen { return }
if let controller = LCEssentials.getTopViewController(aboveBars: true) {
showSnackBar(controller: controller) {
completion?()
}
}
}
}

View File

@@ -0,0 +1,4 @@
struct Repositorio {
var text = "Hello, World!"
private var testBin: [Int] = []
}

View File

@@ -0,0 +1,274 @@
//
// Copyright (c) 2024 Loverde Co.
//
// 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.
#if canImport(SwiftUI)
import SwiftUI
@available(iOS 15, *)
class LCENavigationState: ObservableObject {
@Published var rightButtonImage: AnyView? = nil
@Published var rightButtonText: Text = Text("")
@Published var rightButtonAction: () -> Void = {}
@Published var leftButtonImage: AnyView? = nil
@Published var leftButtonText: Text = Text("")
@Published var leftButtonAction: () -> Void = {}
@Published var hideNavigationBar: Bool = false
@Published var title: (any View) = Text("")
@Published var subTitle: (any View) = Text("")
}
@available(iOS 15, *)
public struct LCENavigationView<Content: View>: View {
@ObservedObject private var state: LCENavigationState
let content: Content
public init(
title: (any View) = Text(""),
subTitle: (any View) = Text(""),
@ViewBuilder content: () -> Content
) {
self.content = content()
self._state = ObservedObject(
wrappedValue: LCENavigationState()
)
self.state.title = title
self.state.subTitle = subTitle
}
public var body: some View {
VStack {
if !state.hideNavigationBar {
NavigationBarView
}
content
}
.navigationBarHidden(true)
}
private var NavigationBarView: some View {
HStack {
NavLeftButton
Spacer()
TitleView
Spacer()
NavRightButton
}
.font(.headline)
.padding()
.background {
Color.clear.ignoresSafeArea(edges: .top)
}
}
private var TitleView: some View {
VStack {
AnyView(state.title)
if (try? state.subTitle.getTag() ?? "hidden" ) != "hidden" {
AnyView(state.subTitle)
}
}
}
private var NavLeftButton: some View {
Button(action: state.leftButtonAction) {
HStack {
if let image = state.leftButtonImage {
image
}
state.leftButtonText
}
}
}
private var NavRightButton: some View {
Button(action: state.rightButtonAction) {
HStack {
state.rightButtonText
if let image = state.rightButtonImage {
image
}
}
}
}
public func setRightButton(
text: Text = Text(""),
image: (any View)? = nil,
action: @escaping () -> Void
) -> LCENavigationView {
if let image {
state.rightButtonImage = AnyView(image)
} else {
state.rightButtonImage = nil
}
state.rightButtonText = text
state.rightButtonAction = action
if let string = state.leftButtonText.string, string.isEmpty {
state.leftButtonText = text.foregroundColor(.clear)
}
if state.leftButtonImage == nil {
state.leftButtonImage = image?.foregroundColor(.clear) as? AnyView
}
return self
}
public func setLeftButton(
text: Text = Text(""),
image: (any View)? = nil,
action: @escaping () -> Void
) -> LCENavigationView {
if let image {
state.leftButtonImage = AnyView(image)
} else {
state.leftButtonImage = nil
}
state.leftButtonText = text
state.leftButtonAction = action
if let string = state.rightButtonText.string, string.isEmpty {
state.rightButtonText = text.foregroundColor(.clear)
}
if state.rightButtonImage == nil {
state.rightButtonImage = image?.foregroundColor(.clear) as? AnyView
}
return self
}
public func setTitle(
text: (any View) = Text(""),
subTitle: (any View)? = nil
) -> LCENavigationView {
state.title = text
state.subTitle = subTitle ?? Text("").tag("hidden")
return self
}
public func hideNavigationView(_ hide: Bool) -> LCENavigationView {
state.hideNavigationBar = hide
return self
}
}
@available(iOS 15.0, *)
extension FormatStyle {
func format(any value: Any) -> FormatOutput? {
if let v = value as? FormatInput {
return format(v)
}
return nil
}
}
@available(iOS 15.0, *)
extension LocalizedStringKey {
var resolved: String? {
let mirror = Mirror(reflecting: self)
guard let key = mirror.descendant("key") as? String else {
return nil
}
guard let args = mirror.descendant("arguments") as? [Any] else {
return nil
}
let values = args.map { arg -> Any? in
let mirror = Mirror(reflecting: arg)
if let value = mirror.descendant("storage", "value", ".0") {
return value
}
guard let format = mirror.descendant("storage", "formatStyleValue", "format") as? any FormatStyle,
let input = mirror.descendant("storage", "formatStyleValue", "input") else {
return nil
}
return format.format(any: input)
}
let va = values.compactMap { arg -> CVarArg? in
switch arg {
case let i as Int: return i
case let i as Int64: return i
case let i as Int8: return i
case let i as Int16: return i
case let i as Int32: return i
case let u as UInt: return u
case let u as UInt64: return u
case let u as UInt8: return u
case let u as UInt16: return u
case let u as UInt32: return u
case let f as Float: return f
case let f as CGFloat: return f
case let d as Double: return d
case let o as NSObject: return o
default: return nil
}
}
if va.count != values.count {
return nil
}
return String.localizedStringWithFormat(key, va)
}
}
@available(iOS 15.0, *)
extension Text {
var string: String? {
let mirror = Mirror(reflecting: self)
if let s = mirror.descendant("storage", "verbatim") as? String {
return s
} else if let attrStr = mirror.descendant("storage", "anyTextStorage", "str") as? AttributedString {
return String(attrStr.characters)
} else if let key = mirror.descendant("storage", "anyTextStorage", "key") as? LocalizedStringKey {
return key.resolved
} else if let format = mirror.descendant("storage", "anyTextStorage", "storage", "format") as? any FormatStyle,
let input = mirror.descendant("storage", "anyTextStorage", "storage", "input") {
return format.format(any: input) as? String
} else if let formatter = mirror.descendant("storage", "anyTextStorage", "formatter") as? Formatter,
let object = mirror.descendant("storage", "anyTextStorage", "object") {
return formatter.string(for: object)
}
return nil
}
}
//@available(iOS 15.0, *)
//struct LCENavigationView_Previews: PreviewProvider {
// static var previews: some View {
// LCENavigationView {
// Text("Hello, World!")
// }
// .setTitle(text: Text("Hellow Nav Title"))
// .setLeftButton(text: "Back") {
// print("Button tapped!")
// }
// }
//}
#endif

View File

@@ -0,0 +1,81 @@
//
// Copyright (c) 2025 Loverde Co.
//
// 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 SwiftUI
@available(iOS 13.0, *)
extension View {
func getTag<TagType: Hashable>() throws -> TagType {
// Mirror this view
let mirror = Mirror(reflecting: self)
// Get tag modifier
guard let realTag = mirror.descendant("modifier", "value") else {
// Not found tag modifier here, this could be composite
// view. Check for modifier directly on the `body` if
// not a primitive view type.
guard Body.self != Never.self else {
throw TagError.notFound
}
return try body.getTag()
}
// Bind memory to extract tag's value
let fakeTag = try withUnsafeBytes(of: realTag) { ptr -> FakeTag<TagType> in
let binded = ptr.bindMemory(to: FakeTag<TagType>.self)
guard let mapped = binded.first else {
throw TagError.other
}
return mapped
}
// Return tag's value
return fakeTag.value
}
func extractTag<TagType: Hashable>(_ closure: (() throws -> TagType) -> Void) -> Self {
closure(getTag)
return self
}
}
enum TagError: Error, CustomStringConvertible {
case notFound
case other
public var description: String {
switch self {
case .notFound: return "Not found"
case .other: return "Other"
}
}
}
enum FakeTag<TagType: Hashable> {
case tagged(TagType)
var value: TagType {
switch self {
case let .tagged(value): return value
}
}
}

BIN
loverde_company_logo_full.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB