1 Commits

Author SHA1 Message Date
Daniel Arantes Loverde
27b6b45ee9 Complete documentation 2025-10-23 13:26:03 -03:00
41 changed files with 3336 additions and 462 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
.DS_Store .DS_Store
/.build /.build
/build
/Packages /Packages
xcuserdata/ xcuserdata/
DerivedData/ DerivedData/

File diff suppressed because it is too large Load Diff

View File

@@ -22,5 +22,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
Autor: Daniel Arantes Loverde

View File

@@ -1,15 +0,0 @@
{
"originHash" : "33e7d52ad13cf774717778548edb365d33ff62d766d0049165bc8970f19a23ef",
"pins" : [
{
"identity" : "lcecryptokitbinary",
"kind" : "remoteSourceControl",
"location" : "https://60c260c85d3a2fe840411b0ff98f521b5eca3c56@git.loverde.com.br/Loverde-Company-LTDA/LCECryptoKitBinary.git",
"state" : {
"revision" : "2c5c47cebef40a8adc5557d071a35be405c05e30",
"version" : "1.0.2"
}
}
],
"version" : 3
}

View File

@@ -1,43 +1,16 @@
// swift-tools-version: 6.0 // swift-tools-version: 6.0
import PackageDescription import PackageDescription
import Foundation
let isLocalDevelopment = false //FileManager.default.fileExists(atPath: "../LCECryptoKit/PrivateLib/LCECryptoKitBinary")
let enableCryptoBinary = ProcessInfo.processInfo.environment["LCE_ENABLE_CRYPTO_BINARY"] != "0"
let cryptoPackageURL = isLocalDevelopment
? "../LCECryptoKit/PrivateLib/LCECryptoKitBinary"
: "https://60c260c85d3a2fe840411b0ff98f521b5eca3c56@git.loverde.com.br/Loverde-Company-LTDA/LCECryptoKitBinary.git"
let packageDependencies: [Package.Dependency] = enableCryptoBinary
? [
.package(url: cryptoPackageURL, exact: "1.0.2")
]
: []
let targetDependencies: [Target.Dependency] = enableCryptoBinary
? [
.product(name: "LCECryptoKit", package: "lcecryptokitbinary")
]
: []
let package = Package( let package = Package(
name: "LCEssentials", name: "LCEssentials",
platforms: [
.iOS(.v13),
.macOS(.v10_15),
.tvOS(.v13),
.watchOS(.v6)
],
products: [ products: [
.library( .library(
name: "LCEssentials", name: "LCEssentials",
targets: ["LCEssentials"]), targets: ["LCEssentials"]),
], ],
dependencies: packageDependencies,
targets: [ targets: [
.target( .target(
name: "LCEssentials", name: "LCEssentials"),
dependencies: targetDependencies),
] ]
) )

View File

@@ -17,47 +17,21 @@ Installation
#### Swift Package Manager (SPM) #### Swift Package Manager (SPM)
``` swift ``` swift
dependencies: [ dependencies: [
.package(url: "https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials", .upToNextMajor(from: "1.0.0")) .package(url: "http://git.loverde.com.br:3000/git/LCEssentials.git", .upToNextMajor(from: "1.0.0"))
] ]
``` ```
You can also add it via XCode SPM editor with URL: You can also add it via XCode SPM editor with URL:
``` swift ``` swift
https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials http://git.loverde.com.br:3000/git/LCEssentials.git
``` ```
## Usage example ## Usage example
[Read full documentation](LCEssentials_DOCUMENTATION.md)
* Background Trhead Author:
----
```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
```
Any question or doubts, please send thru email Any question or doubts, please send thru email
@@ -65,5 +39,3 @@ 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 "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) [![Alt text](https://loverde.com.br/_signature/loverde_github_mail.gif "Loverde Co. Github")](https://github.com/loverde-co)
Autor: Daniel Arantes Loverde

View File

@@ -5,10 +5,8 @@
// Created by iOSDevCenters on 11/12/15. // Created by iOSDevCenters on 11/12/15.
// Copyright © 2016 iOSDevCenters. All rights reserved. // Copyright © 2016 iOSDevCenters. All rights reserved.
// //
#if canImport(UIKit)
import UIKit import UIKit
#endif #if os(iOS) || os(macOS)
#if canImport(UIKit)
import ImageIO import ImageIO
//let jeremyGif = UIImage.gifWithName("jeremy") //let jeremyGif = UIImage.gifWithName("jeremy")

View File

@@ -1,96 +0,0 @@
//
// 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
#if canImport(LCECryptoKit)
import LCECryptoKit
public final class LCECryptoKitManager {
private let hashKey: String
public init() {
self.hashKey = ""
}
public init(privateKey: String){
self.hashKey = privateKey
}
public static func generateKey() -> String {
LCECryptoKit.generateRandomAESKeyString()
}
public func encodeTP(email: String, password: String) -> String? {
return LCECryptoKit.encodeSeed(email: email, password: password)
}
public func decodeOTP(_ otpHash: String) -> String? {
return LCECryptoKit.decodeSeed(otpKey: otpHash)
}
// MARK: Need hashKey to decode
public func encodeOTPWithKey(email: String, password: String) -> String? {
return LCECryptoKit.encodeSeed(email: email, password: password, hashKey: self.hashKey)
}
public func decodeOTPWithKey(_ otpHash: String) -> Bool {
LCECryptoKit.decodeSeed(otpKey: otpHash, hashKey: self.hashKey)
}
}
#else
public final class LCECryptoKitManager {
private let hashKey: String
public init() {
self.hashKey = ""
}
public init(privateKey: String){
self.hashKey = privateKey
}
public static func generateKey() -> String {
""
}
public func encodeTP(email: String, password: String) -> String? {
nil
}
public func decodeOTP(_ otpHash: String) -> String? {
nil
}
public func encodeOTPWithKey(email: String, password: String) -> String? {
nil
}
public func decodeOTPWithKey(_ otpHash: String) -> Bool {
false
}
}
#endif

View File

@@ -28,7 +28,6 @@ import Security
#if canImport(UIKit) #if canImport(UIKit)
import UIKit import UIKit
#endif #endif
#endif
/// A generic `Result` enumeration to represent either a success `Value` or a failure `Error`. /// A generic `Result` enumeration to represent either a success `Value` or a failure `Error`.
public enum Result<Value, Error: Swift.Error> { public enum Result<Value, Error: Swift.Error> {
@@ -46,8 +45,6 @@ public enum httpMethod: String {
case get = "GET" case get = "GET"
/// The PUT method. /// The PUT method.
case put = "PUT" case put = "PUT"
/// The PATCH method.
case patch = "PATCH"
/// The DELETE method. /// The DELETE method.
case delete = "DELETE" case delete = "DELETE"
} }
@@ -91,7 +88,7 @@ public struct API {
/// - Parameters: /// - Parameters:
/// - url: The URL string for the request. /// - url: The URL string for the request.
/// - params: Optional parameters for the request. Can be `[String: Any]` for JSON/form-data, or `Data` for raw body. /// - params: Optional parameters for the request. Can be `[String: Any]` for JSON/form-data, or `Data` for raw body.
/// - method: The HTTP method to use for the request (`.get`, `.post`, `.put`, `.delete`,`.patch` ). /// - method: The HTTP method to use for the request (`.get`, `.post`, `.put`, `.delete`).
/// - headers: Optional custom HTTP headers to be added to the request. These override default headers if there are conflicts. /// - headers: Optional custom HTTP headers to be added to the request. These override default headers if there are conflicts.
/// - jsonEncoding: A boolean indicating whether parameters should be JSON encoded. Defaults to `true`. /// - jsonEncoding: A boolean indicating whether parameters should be JSON encoded. Defaults to `true`.
/// - debug: A boolean indicating whether to print debug logs for the request and response. Defaults to `true`. /// - debug: A boolean indicating whether to print debug logs for the request and response. Defaults to `true`.
@@ -111,8 +108,8 @@ public struct API {
persistConnection: Bool = false) async throws -> T { persistConnection: Bool = false) async throws -> T {
if let urlReq = URL(string: url.replaceURL(params as? [String: Any] ?? [:] )) { if let urlReq = URL(string: url.replaceURL(params as? [String: Any] ?? [:] )) {
var request = URLRequest(url: urlReq, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: timeoutInterval) var request = URLRequest(url: urlReq, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
if method == .post || method == .put || method == .delete || method == .patch { if method == .post || method == .put || method == .delete {
if let params = params as? [String: Any], if let params = params as? [String: Any],
let pathFile = params["file"] as? String, let pathFile = params["file"] as? String,
let fileURL = URL(string: pathFile) { let fileURL = URL(string: pathFile) {
@@ -132,26 +129,15 @@ public struct API {
// Add the file // Add the file
let fileName = fileURL.lastPathComponent let fileName = fileURL.lastPathComponent
let mimeType = mimeTypeForPath(path: fileName) let mimeType = mimeTypeForPath(path: fileName)
printInfo(title: "Body size before", msg: "\(body.count) bytes")
let fileData: Data
do {
fileData = try Data(contentsOf: fileURL)
printInfo(title: "Body size after", msg: "\(body.count) bytes")
} catch {
printError(title: "Upload File", msg: error.localizedDescription)
throw error
}
do { do {
let fileData = try Data(contentsOf: fileURL)
body.append("--\(boundary)\r\n".data(using: .utf8)!) 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-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("Content-Type: \(mimeType)\r\n\r\n".data(using: .utf8)!)
let fileDataCopy = Data(fileData) body.append(fileData)
body.append(fileDataCopy) body.append("\r\n".data(using: .utf8)!)
let dataUTF8 = "\r\n".data(using: .utf8)! } catch {
body.append(dataUTF8) printError(title: "Upload File", msg: error.localizedDescription)
printInfo(title: "Body size after", msg: "\(body.count) bytes")
} }
// Finalize the request body // Finalize the request body

View File

@@ -21,12 +21,8 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if canImport(AVFoundation)
import AVFoundation import AVFoundation
#endif
#if os(watchOS) #if os(watchOS)
import WatchKit import WatchKit
#endif #endif
@@ -159,7 +155,7 @@ public struct LCEssentials {
diskPath: "file_cache" diskPath: "file_cache"
) )
#if canImport(UIKit) #if os(iOS) || os(macOS)
/// Extract the file name from the file path /// Extract the file name from the file path
/// ///
/// - Parameter filePath: Full file path in bundle /// - Parameter filePath: Full file path in bundle
@@ -270,11 +266,7 @@ public struct LCEssentials {
/// - LoverdeCo: Check if app is running in debug mode. /// - LoverdeCo: Check if app is running in debug mode.
@MainActor @MainActor
public static var isInDebuggingMode: Bool { public static var isInDebuggingMode: Bool {
#if canImport(UIKit)
return UIApplication.inferredEnvironment == .debug return UIApplication.inferredEnvironment == .debug
#else
return false
#endif
} }
#if !os(macOS) #if !os(macOS)
@@ -424,7 +416,7 @@ public struct LCEssentials {
// MARK: - Methods // MARK: - Methods
public extension LCEssentials { public extension LCEssentials {
#if canImport(UIKit) #if os(iOS) || os(macOS)
/// - LoverdeCo: Share link with message /// - LoverdeCo: Share link with message
/// ///
/// - Parameters: /// - Parameters:

View File

@@ -20,9 +20,7 @@
// THE SOFTWARE. // THE SOFTWARE.
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
@objc public protocol LCESingletonDelegate: AnyObject { @objc public protocol LCESingletonDelegate: AnyObject {
@objc optional func singleton(object: Any?, withData: Any) @objc optional func singleton(object: Any?, withData: Any)

View File

@@ -21,9 +21,7 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
// MARK: - Methods (Equatable) // MARK: - Methods (Equatable)
public extension Array where Element: Equatable { public extension Array where Element: Equatable {
@@ -150,7 +148,6 @@ public extension Array where Element: Equatable {
} }
} }
#if canImport(UIKit)
extension Array where Element == NSLayoutConstraint { extension Array where Element == NSLayoutConstraint {
@MainActor @MainActor
@@ -172,5 +169,3 @@ extension Array where Element == NSLayoutConstraint {
} }
} }
} }
#endif

View File

@@ -21,12 +21,8 @@
import Foundation import Foundation
#if canImport(CryptoKit)
import CryptoKit import CryptoKit
#endif
#if canImport(CommonCrypto)
import CommonCrypto import CommonCrypto
#endif
public extension Data { public extension Data {
@@ -75,25 +71,17 @@ public extension Data {
@available(iOS 13.0, *) @available(iOS 13.0, *)
func HMACSHA512(key: Data) -> Data { func HMACSHA512(key: Data) -> Data {
#if canImport(CryptoKit)
var hmac = HMAC<SHA512>.init(key: SymmetricKey(data: key)) var hmac = HMAC<SHA512>.init(key: SymmetricKey(data: key))
hmac.update(data: self) hmac.update(data: self)
return Data(hmac.finalize()) return Data(hmac.finalize())
#else
return Data()
#endif
} }
func SHA512() -> Data { func SHA512() -> Data {
#if canImport(CommonCrypto)
var digest = [UInt8](repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH)) var digest = [UInt8](repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH))
self.withUnsafeBytes { self.withUnsafeBytes {
_ = CC_SHA512($0.baseAddress, CC_LONG(self.count), &digest) _ = CC_SHA512($0.baseAddress, CC_LONG(self.count), &digest)
} }
return Data(digest) return Data(digest)
#else
return Data()
#endif
} }
func XOR(with other: Data) -> Data { func XOR(with other: Data) -> Data {
@@ -101,15 +89,11 @@ public extension Data {
} }
func SHA256() -> Data { func SHA256() -> Data {
#if canImport(CommonCrypto)
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
self.withUnsafeBytes { self.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(self.count), &digest) _ = CC_SHA256($0.baseAddress, CC_LONG(self.count), &digest)
} }
return Data(digest) return Data(digest)
#else
return Data()
#endif
} }
func object<T: Codable>() -> T? { func object<T: Codable>() -> T? {
@@ -140,13 +124,9 @@ public extension Data {
///print("md5Hex: \(md5Hex)") ///print("md5Hex: \(md5Hex)")
@available(iOS 13.0, *) @available(iOS 13.0, *)
static func MD5(string: String) -> Data { static func MD5(string: String) -> Data {
#if canImport(CryptoKit)
let messageData = string.data(using: .utf8)! let messageData = string.data(using: .utf8)!
let digestData = Insecure.MD5.hash (data: messageData) let digestData = Insecure.MD5.hash (data: messageData)
let digestHex = String(digestData.map { String(format: "%02hhx", $0) }.joined().prefix(32)) let digestHex = String(digestData.map { String(format: "%02hhx", $0) }.joined().prefix(32))
return Data(digestHex.utf8) return Data(digestHex.utf8)
#else
return Data()
#endif
} }
} }

View File

@@ -21,9 +21,7 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
public extension Dictionary { public extension Dictionary {

View File

@@ -21,9 +21,7 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
public extension FileManager { public extension FileManager {
@@ -56,7 +54,7 @@ public extension FileManager {
} }
} }
#if canImport(UIKit) #if os(iOS) || os(macOS)
func saveImageToDirectory( _ imageWithPath : String, imagem : UIImage ) -> Bool { func saveImageToDirectory( _ imageWithPath : String, imagem : UIImage ) -> Bool {
let data = imagem.pngData() let data = imagem.pngData()

View File

@@ -21,11 +21,8 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if canImport(UIKit)
extension NSLayoutConstraint { extension NSLayoutConstraint {
func constraintWithMultiplier(_ multiplier: CGFloat) -> NSLayoutConstraint { func constraintWithMultiplier(_ multiplier: CGFloat) -> NSLayoutConstraint {
@@ -75,4 +72,3 @@ extension NSLayoutConstraint {
return false return false
} }
} }
#endif

View File

@@ -21,10 +21,8 @@
import Foundation import Foundation
#if canImport(UIKit) #if os(iOS) || os(macOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
public extension NSMutableAttributedString { public extension NSMutableAttributedString {
@discardableResult func customize(_ text: String, @discardableResult func customize(_ text: String,

View File

@@ -20,9 +20,7 @@
// THE SOFTWARE. // THE SOFTWARE.
#if canImport(UIKit)
import UIKit import UIKit
#endif
public extension Optional { public extension Optional {
/// Get self of default value (if self is nil). /// Get self of default value (if self is nil).

View File

@@ -21,9 +21,7 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if canImport(CommonCrypto) #if canImport(CommonCrypto)
import CommonCrypto import CommonCrypto
#endif #endif
@@ -50,14 +48,10 @@ public extension String {
} }
var convertToHTML: NSAttributedString? { var convertToHTML: NSAttributedString? {
#if canImport(UIKit)
return convertHtmlToAttributedStringWithCSS(font: nil, return convertHtmlToAttributedStringWithCSS(font: nil,
csscolor: "", csscolor: "",
lineheight: 0, lineheight: 0,
csstextalign: "") csstextalign: "")
#else
return convertHtmlToNSAttributedString
#endif
} }
/// Check if string is a valid URL. /// Check if string is a valid URL.
@@ -652,7 +646,7 @@ public extension String {
return strDate?.date(withCurrFormatt: newFormatt, localeIdentifier: localeIdentifier, timeZone: timeZone) return strDate?.date(withCurrFormatt: newFormatt, localeIdentifier: localeIdentifier, timeZone: timeZone)
} }
#if canImport(UIKit) #if os(iOS) || os(macOS)
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat { func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude) let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil) let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
@@ -763,7 +757,7 @@ public extension String {
} }
return self return self
} }
#if canImport(UIKit)
/// Converte String para HTML com CSS. /// Converte String para HTML com CSS.
/// ///
/// - Parameters: /// - Parameters:
@@ -807,7 +801,6 @@ public extension String {
return nil return nil
} }
} }
#endif
/// Float value from string (if applicable). /// Float value from string (if applicable).
/// ///

View File

@@ -19,10 +19,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE. // THE SOFTWARE.
#if canImport(UIKit) #if canImport(UIKit) && os(iOS) || os(macOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
public extension UIApplication { public extension UIApplication {
/// Application running environment. /// Application running environment.

View File

@@ -21,11 +21,9 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if canImport(UIKit) #if os(iOS) || os(macOS)
// MARK: - Properties // MARK: - Properties
public extension UIButton { public extension UIButton {

View File

@@ -21,10 +21,8 @@
import Foundation import Foundation
#if canImport(UIKit) #if os(iOS) || os(macOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
public extension UICollectionView { public extension UICollectionView {

View File

@@ -21,11 +21,9 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if canImport(UIKit) #if os(iOS) || os(macOS)
public extension UIColor { public extension UIColor {
var redValue: CGFloat{ return CIColor(color: self).red } var redValue: CGFloat{ return CIColor(color: self).red }

View File

@@ -21,11 +21,9 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if canImport(UIKit) #if os(iOS) || os(macOS)
public extension UIDevice { public extension UIDevice {
static var topNotch: CGFloat { static var topNotch: CGFloat {
@@ -66,104 +64,59 @@ public extension UIDevice {
#if os(iOS) #if os(iOS)
switch identifier { switch identifier {
case "iPod5,1": return "iPod touch (5th generation)" case "iPod5,1": return "iPod touch (5th generation)"
case "iPod7,1": return "iPod touch (6th generation)" case "iPod7,1": return "iPod touch (6th generation)"
case "iPod9,1": return "iPod touch (7th generation)" case "iPod9,1": return "iPod touch (7th generation)"
case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4" case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4"
case "iPhone4,1": return "iPhone 4s" case "iPhone4,1": return "iPhone 4s"
case "iPhone5,1", "iPhone5,2": return "iPhone 5" case "iPhone5,1", "iPhone5,2": return "iPhone 5"
case "iPhone5,3", "iPhone5,4": return "iPhone 5c" case "iPhone5,3", "iPhone5,4": return "iPhone 5c"
case "iPhone6,1", "iPhone6,2": return "iPhone 5s" case "iPhone6,1", "iPhone6,2": return "iPhone 5s"
case "iPhone7,2": return "iPhone 6" case "iPhone7,2": return "iPhone 6"
case "iPhone7,1": return "iPhone 6 Plus" case "iPhone7,1": return "iPhone 6 Plus"
case "iPhone8,1": return "iPhone 6s" case "iPhone8,1": return "iPhone 6s"
case "iPhone8,2": return "iPhone 6s Plus" case "iPhone8,2": return "iPhone 6s Plus"
case "iPhone9,1", "iPhone9,3": return "iPhone 7" case "iPhone9,1", "iPhone9,3": return "iPhone 7"
case "iPhone9,2", "iPhone9,4": return "iPhone 7 Plus" case "iPhone9,2", "iPhone9,4": return "iPhone 7 Plus"
case "iPhone10,1", "iPhone10,4": return "iPhone 8" case "iPhone8,4", "iPhone12,8": return "iPhone SE"
case "iPhone10,2", "iPhone10,5": return "iPhone 8 Plus" case "iPhone10,1", "iPhone10,4": return "iPhone 8"
case "iPhone10,3", "iPhone10,6": return "iPhone X" case "iPhone10,2", "iPhone10,5": return "iPhone 8 Plus"
case "iPhone11,2": return "iPhone XS" case "iPhone10,3", "iPhone10,6": return "iPhone X"
case "iPhone11,4", "iPhone11,6": return "iPhone XS Max" case "iPhone11,2": return "iPhone XS"
case "iPhone11,8": return "iPhone XR" case "iPhone11,4", "iPhone11,6": return "iPhone XS Max"
case "iPhone12,1": return "iPhone 11" case "iPhone11,8": return "iPhone XR"
case "iPhone12,3": return "iPhone 11 Pro" case "iPhone12,1": return "iPhone 11"
case "iPhone12,5": return "iPhone 11 Pro Max" case "iPhone12,3": return "iPhone 11 Pro"
case "iPhone13,1": return "iPhone 12 mini" case "iPhone12,5": return "iPhone 11 Pro Max"
case "iPhone13,2": return "iPhone 12" case "iPhone13,1": return "iPhone 12 mini"
case "iPhone13,3": return "iPhone 12 Pro" case "iPhone13,2": return "iPhone 12"
case "iPhone13,4": return "iPhone 12 Pro Max" case "iPhone13,3": return "iPhone 12 Pro"
case "iPhone14,4": return "iPhone 13 mini" case "iPhone13,4": return "iPhone 12 Pro Max"
case "iPhone14,5": return "iPhone 13" case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return "iPad 2"
case "iPhone14,2": return "iPhone 13 Pro" case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad 3"
case "iPhone14,3": return "iPhone 13 Pro Max" case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad 4"
case "iPhone14,7": return "iPhone 14" case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air"
case "iPhone14,8": return "iPhone 14 Plus" case "iPad5,3", "iPad5,4": return "iPad Air 2"
case "iPhone15,2": return "iPhone 14 Pro" case "iPad6,11", "iPad6,12": return "iPad 5"
case "iPhone15,3": return "iPhone 14 Pro Max" case "iPad7,5", "iPad7,6": return "iPad 6"
case "iPhone15,4": return "iPhone 15" case "iPad7,11", "iPad7,12": return "iPad 7"
case "iPhone15,5": return "iPhone 15 Plus" case "iPad11,4", "iPad11,5": return "iPad Air (3rd generation)"
case "iPhone16,1": return "iPhone 15 Pro" case "iPad2,5", "iPad2,6", "iPad2,7": return "iPad Mini"
case "iPhone16,2": return "iPhone 15 Pro Max" case "iPad4,4", "iPad4,5", "iPad4,6": return "iPad Mini 2"
case "iPhone17,3": return "iPhone 16" case "iPad4,7", "iPad4,8", "iPad4,9": return "iPad Mini 3"
case "iPhone17,4": return "iPhone 16 Plus" case "iPad5,1", "iPad5,2": return "iPad Mini 4"
case "iPhone17,1": return "iPhone 16 Pro" case "iPad11,1", "iPad11,2": return "iPad Mini 5"
case "iPhone17,2": return "iPhone 16 Pro Max" case "iPad6,3", "iPad6,4": return "iPad Pro (9.7-inch)"
case "iPhone17,5": return "iPhone 16e" case "iPad6,7", "iPad6,8": return "iPad Pro (12.9-inch)"
case "iPhone18,3": return "iPhone 17" case "iPad7,1", "iPad7,2": return "iPad Pro (12.9-inch) (2nd generation)"
case "iPhone18,4": return "iPhone Air" case "iPad7,3", "iPad7,4": return "iPad Pro (10.5-inch)"
case "iPhone18,1": return "iPhone 17 Pro" case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4":return "iPad Pro (11-inch)"
case "iPhone18,2": return "iPhone 17 Pro Max" case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8":return "iPad Pro (12.9-inch) (3rd generation)"
case "iPhone8,4": return "iPhone SE" case "AppleTV5,3": return "Apple TV"
case "iPhone12,8": return "iPhone SE (2nd generation)" case "AppleTV6,2": return "Apple TV 4K"
case "iPhone14,6": return "iPhone SE (3rd generation)" case "AudioAccessory1,1": return "HomePod"
case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return "iPad 2" case "i386", "x86_64": return "Simulator \(identifier)"
case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad (3rd generation)" default: return identifier
case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad (4th generation)"
case "iPad6,11", "iPad6,12": return "iPad (5th generation)"
case "iPad7,5", "iPad7,6": return "iPad (6th generation)"
case "iPad7,11", "iPad7,12": return "iPad (7th generation)"
case "iPad11,6", "iPad11,7": return "iPad (8th generation)"
case "iPad12,1", "iPad12,2": return "iPad (9th generation)"
case "iPad13,18", "iPad13,19": return "iPad (10th generation)"
case "iPad15,7", "iPad15,8": return "iPad (11th generation)"
case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air"
case "iPad5,3", "iPad5,4": return "iPad Air 2"
case "iPad11,3", "iPad11,4": return "iPad Air (3rd generation)"
case "iPad13,1", "iPad13,2": return "iPad Air (4th generation)"
case "iPad13,16", "iPad13,17": return "iPad Air (5th generation)"
case "iPad14,8", "iPad14,9": return "iPad Air (11-inch) (M2)"
case "iPad14,10", "iPad14,11": return "iPad Air (13-inch) (M2)"
case "iPad15,3", "iPad15,4": return "iPad Air (11-inch) (M3)"
case "iPad15,5", "iPad15,6": return "iPad Air (13-inch) (M3)"
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 (5th generation)"
case "iPad14,1", "iPad14,2": return "iPad mini (6th generation)"
case "iPad16,1", "iPad16,2": return "iPad mini (A17 Pro)"
case "iPad6,3", "iPad6,4": return "iPad Pro (9.7-inch)"
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) (1st generation)"
case "iPad8,9", "iPad8,10": return "iPad Pro (11-inch) (2nd generation)"
case "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7": return "iPad Pro (11-inch) (3rd generation)"
case "iPad14,3", "iPad14,4": return "iPad Pro (11-inch) (4th generation)"
case "iPad16,3", "iPad16,4": return "iPad Pro (11-inch) (M4)"
case "iPad17,1", "iPad17,2": return "iPad Pro (11-inch) (M5)"
case "iPad6,7", "iPad6,8": return "iPad Pro (12.9-inch) (1st generation)"
case "iPad7,1", "iPad7,2": return "iPad Pro (12.9-inch) (2nd generation)"
case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8": return "iPad Pro (12.9-inch) (3rd generation)"
case "iPad8,11", "iPad8,12": return "iPad Pro (12.9-inch) (4th generation)"
case "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11":return "iPad Pro (12.9-inch) (5th generation)"
case "iPad14,5", "iPad14,6": return "iPad Pro (12.9-inch) (6th generation)"
case "iPad16,5", "iPad16,6": return "iPad Pro (13-inch) (M4)"
case "iPad17,3", "iPad17,4": return "iPad Pro (13-inch) (M5)"
case "AppleTV5,3": return "Apple TV"
case "AppleTV6,2": return "Apple TV 4K"
case "AudioAccessory1,1": return "HomePod"
case "AudioAccessory5,1": return "HomePod mini"
default: return identifier
} }
#endif #endif
} }

View File

@@ -21,11 +21,9 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if canImport(UIKit) #if os(iOS) || os(macOS)
public extension UIImage { public extension UIImage {
//Extension Required by RoundedButton to create UIImage from UIColor //Extension Required by RoundedButton to create UIImage from UIColor
func imageWithColor(color: UIColor) -> UIImage { func imageWithColor(color: UIColor) -> UIImage {

View File

@@ -21,11 +21,9 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if canImport(UIKit) #if os(iOS) || os(macOS)
@MainActor @MainActor
public extension UIImageView { public extension UIImageView {

View File

@@ -21,11 +21,9 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if canImport(UIKit) #if os(iOS) || os(macOS)
public extension UILabel { public extension UILabel {
func lineNumbers() -> Int{ func lineNumbers() -> Int{

View File

@@ -21,10 +21,8 @@
import Foundation import Foundation
#if canImport(UIKit) #if os(iOS) || os(macOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
import QuartzCore import QuartzCore
public extension UINavigationController { public extension UINavigationController {

View File

@@ -21,10 +21,8 @@
import Foundation import Foundation
#if canImport(UIKit) #if os(iOS) || os(macOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
public extension UIResponder { public extension UIResponder {

View File

@@ -21,9 +21,7 @@
#if canImport(UIKit) && !os(watchOS) #if canImport(UIKit) && !os(watchOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
// MARK: - Methods // MARK: - Methods
public extension UIScrollView { public extension UIScrollView {

View File

@@ -21,9 +21,7 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if os(iOS) #if os(iOS)
// MARK: - Initializers // MARK: - Initializers

View File

@@ -21,10 +21,8 @@
import Foundation import Foundation
#if canImport(UIKit) #if os(iOS) || os(macOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
public class CustomTabBadge: UILabel { public class CustomTabBadge: UILabel {

View File

@@ -21,11 +21,9 @@
import Foundation import Foundation
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if canImport(UIKit) #if os(iOS) || os(macOS)
//MARK: - UITableView Animation Cell //MARK: - UITableView Animation Cell
public typealias UITableViewCellAnimation = (UITableViewCell, IndexPath, UITableView) -> Void public typealias UITableViewCellAnimation = (UITableViewCell, IndexPath, UITableView) -> Void

View File

@@ -21,10 +21,8 @@
import Foundation import Foundation
#if canImport(UIKit) #if canImport(UIKit) && os(iOS) || os(macOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
public extension UITapGestureRecognizer { public extension UITapGestureRecognizer {

View File

@@ -21,10 +21,8 @@
import Foundation import Foundation
#if canImport(UIKit) #if os(iOS) || os(macOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
public extension UITextField { public extension UITextField {

View File

@@ -21,10 +21,8 @@
import Foundation import Foundation
#if canImport(UIKit) #if os(iOS) || os(macOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint) typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)

View File

@@ -22,10 +22,8 @@
import Foundation import Foundation
#if canImport(UIKit) #if os(iOS) || os(macOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
import QuartzCore import QuartzCore
public enum ToastPosition { public enum ToastPosition {

View File

@@ -20,10 +20,8 @@
// THE SOFTWARE. // THE SOFTWARE.
#if canImport(UIKit) #if os(iOS) || os(macOS)
#if canImport(UIKit)
import UIKit import UIKit
#endif
import AVFoundation import AVFoundation
import Photos import Photos

View File

@@ -19,11 +19,9 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE. // THE SOFTWARE.
#if canImport(UIKit)
import UIKit import UIKit
#endif
#if canImport(UIKit) #if os(iOS) || os(macOS)
/// A protocol for delegates of `ImageZoomController` to provide callbacks for zoom and close events. /// A protocol for delegates of `ImageZoomController` to provide callbacks for zoom and close events.
@objc public protocol ImageZoomControllerDelegate { @objc public protocol ImageZoomControllerDelegate {
/// Called when the image in the controller is zoomed. /// Called when the image in the controller is zoomed.

View File

@@ -20,8 +20,6 @@
// THE SOFTWARE. // THE SOFTWARE.
// MARK: - Framework headers // MARK: - Framework headers
#if canImport(UIKit)
import Foundation
import UIKit import UIKit
// MARK: - Protocols // MARK: - Protocols
@@ -233,36 +231,53 @@ public extension LCSnackBarView {
/// Handles the `keyboardWillShowNotification` to adjust the snackbar's position. /// Handles the `keyboardWillShowNotification` to adjust the snackbar's position.
/// - Parameter notification: The `Notification` object containing keyboard information. /// - Parameter notification: The `Notification` object containing keyboard information.
@MainActor @objc private func keyboardWillShow(_ notification: Notification?) -> Void {
@objc private func keyboardWillShow(_ notification: Notification?) {
guard let info = notification?.userInfo else { return }
systemKeyboardVisible = true if let info = notification?.userInfo {
let animationCurveRaw = (info[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue systemKeyboardVisible = true
?? UInt(UIView.AnimationCurve.easeOut.rawValue) //
let animationDuration = (info[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue let curveUserInfoKey = UIResponder.keyboardAnimationCurveUserInfoKey
?? 0.25 let durationUserInfoKey = UIResponder.keyboardAnimationDurationUserInfoKey
let keyboardFrame = (info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue let frameEndUserInfoKey = UIResponder.keyboardFrameEndUserInfoKey
?? .zero //
let height = keyboardFrame.height var animationCurve: UIView.AnimationOptions = .curveEaseOut
let animationCurve = UIView.AnimationOptions(rawValue: animationCurveRaw << 16) var animationDuration: TimeInterval = 0.25
var height:CGFloat = 0.0
UIView.animate( // Getting keyboard animation.
withDuration: animationDuration, if let curve = info[curveUserInfoKey] as? UIView.AnimationOptions {
delay: 0, animationCurve = curve
options: animationCurve }
) { [weak self] in
self?.frame.origin.y += height // 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
})
}
} }
} }
/// Handles the `keyboardWillHideNotification`. /// Handles the `keyboardWillHideNotification`.
/// - Parameter notification: The `Notification` object. /// - Parameter notification: The `Notification` object.
@MainActor @objc private func keyboardWillHide(_ notification: Notification?) -> Void {
@objc private func keyboardWillHide(_ notification: Notification?) { DispatchQueue.main.async { [weak self] in
systemKeyboardVisible = false self?.systemKeyboardVisible = false
// keyboard is hidded // keyboard is hidded
}
} }
/// Updates the snackbar's style properties, such as width and corner radius, based on `_style`. /// Updates the snackbar's style properties, such as width and corner radius, based on `_style`.
@@ -497,5 +512,3 @@ public extension LCSnackBarView {
} }
} }
} }
#endif

View File

@@ -18,7 +18,7 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // 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 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE. // THE SOFTWARE.
#if canImport(SwiftUI) && os(iOS) #if canImport(SwiftUI)
import SwiftUI import SwiftUI
/// `LCENavigationState` is an `ObservableObject` that manages the state for `LCENavigationView`. /// `LCENavigationState` is an `ObservableObject` that manages the state for `LCENavigationView`.
@@ -47,8 +47,6 @@ class LCENavigationState: ObservableObject {
@Published var title: (any View) = Text("") @Published var title: (any View) = Text("")
/// The subtitle view of the navigation bar. /// The subtitle view of the navigation bar.
@Published var subTitle: (any View) = Text("") @Published var subTitle: (any View) = Text("")
/// The background color of the navigation bar.
@Published var navigationBarBackgroundColor: Color = .clear
} }
/// `LCENavigationView` is a SwiftUI `View` that provides a customizable navigation bar. /// `LCENavigationView` is a SwiftUI `View` that provides a customizable navigation bar.
@@ -81,7 +79,7 @@ public struct LCENavigationView<Content: View>: View {
/// The body of the `LCENavigationView`. /// The body of the `LCENavigationView`.
public var body: some View { public var body: some View {
VStack(spacing: 0) { VStack {
if !state.hideNavigationBar { if !state.hideNavigationBar {
NavigationBarView NavigationBarView
} }
@@ -102,7 +100,7 @@ public struct LCENavigationView<Content: View>: View {
.font(.headline) .font(.headline)
.padding() .padding()
.background { .background {
state.navigationBarBackgroundColor.ignoresSafeArea(edges: .top) Color.clear.ignoresSafeArea(edges: .top)
} }
} }
@@ -219,14 +217,6 @@ public struct LCENavigationView<Content: View>: View {
state.hideNavigationBar = hide state.hideNavigationBar = hide
return self return self
} }
/// Sets the background color for the navigation bar.
/// - Parameter color: The color to use as the navigation bar background.
/// - Returns: The `LCENavigationView` instance for chaining.
public func setNavigationBarBackgroundColor(_ color: Color) -> LCENavigationView {
state.navigationBarBackgroundColor = color
return self
}
} }
/// Extension to `FormatStyle` to format any value as a string. /// Extension to `FormatStyle` to format any value as a string.