Version 1.0.0
This commit is contained in:
602
Sources/LCEssentials/Classes/LCEssentials.swift
Normal file
602
Sources/LCEssentials/Classes/LCEssentials.swift
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user