diff --git a/OYExtensions.podspec b/OYExtensions.podspec index 6190cda..e7a23bd 100644 --- a/OYExtensions.podspec +++ b/OYExtensions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "OYExtensions" - s.version = "1.0.2" + s.version = "1.0.3" s.summary = "Some useful extension for Swift" s.homepage = "https://github.com/osmanyildirim/OYExtensions.git" diff --git a/Sources/Core/OYDateFormat.swift b/Sources/Core/OYDateFormat.swift index 061a39e..f9f2e24 100644 --- a/Sources/Core/OYDateFormat.swift +++ b/Sources/Core/OYDateFormat.swift @@ -13,6 +13,9 @@ public enum OYDateFormat { /// `dd-MM-yyyy` case short + + /// `dd.MM.yy` + case shortest /// `dd.MM.yyyy` case shortDot @@ -53,6 +56,7 @@ extension OYDateFormat { switch self { case .`default`: return "dd.MM.yyyy HH:mm:ss" case .short: return "dd-MM-yyyy" + case .shortest: return "dd.MM.yy" case .shortDot: return "dd.MM.yyyy" case .time: return "HH:mm:ss" case .isoYear: return "yyyy" diff --git a/Sources/Core/OYError.swift b/Sources/Core/OYError.swift index 9379ba9..5900480 100644 --- a/Sources/Core/OYError.swift +++ b/Sources/Core/OYError.swift @@ -61,4 +61,7 @@ public enum OYError: Error { /// User denies authorization to access tracking case advertisingTrackingIsNotEnabled + + /// Permission could not be declared + case permissionError } diff --git a/Sources/CoreGraphics/UIEdgeInsets+Extensions.swift b/Sources/CoreGraphics/UIEdgeInsets+Extensions.swift new file mode 100644 index 0000000..c32f9a2 --- /dev/null +++ b/Sources/CoreGraphics/UIEdgeInsets+Extensions.swift @@ -0,0 +1,43 @@ +// +// UIEdgeInsets+Extensions.swift +// OYExtensions +// +// Created by osmanyildirim +// + +import UIKit + +extension UIEdgeInsets { + /// `UIEdgeInsets.oy_init(0, 10, 20, 30)` → output → UIEdgeInsets(top: 0.0, left: 10.0, bottom: 20.0, right: 30.0) + public static func oy_init(_ top: CGFloat, _ left: CGFloat, _ bottom: CGFloat, _ right: CGFloat) -> UIEdgeInsets { + UIEdgeInsets(top: top, left: left, bottom: bottom, right: right) + } + + /// let value = UIEdgeInsets(top: 0, left: 10, bottom: 20, right: 30) + /// `value.oy_top` → output → 0.0 + public var oy_top: CGFloat { + get { top } + set(value) { top = value } + } + + /// let value = UIEdgeInsets(top: 0, left: 10, bottom: 20, right: 30) + /// `value.oy_bottom` → output → 20.0 + public var oy_bottom: CGFloat { + get { bottom } + set(value) { bottom = value } + } + + /// let value = UIEdgeInsets(top: 0, left: 10, bottom: 20, right: 30) + /// `value.oy_left` → output → 10.0 + public var oy_left: CGFloat { + get { self.left } + set(value) { self.left = value } + } + + /// let value = UIEdgeInsets(top: 0, left: 10, bottom: 20, right: 30) + /// `value.oy_right` → output → 30.0 + public var oy_right: CGFloat { + get { self.right } + set(value) { self.right = value } + } +} diff --git a/Sources/CoreGraphics/UIRectCorner+Extensions.swift b/Sources/CoreGraphics/UIRectCorner+Extensions.swift new file mode 100644 index 0000000..8880dee --- /dev/null +++ b/Sources/CoreGraphics/UIRectCorner+Extensions.swift @@ -0,0 +1,25 @@ +// +// UIRectCorner+Extensions.swift +// OYExtensions +// +// Created by osmanyildirim +// + +import Foundation + +extension UIRectCorner { + public var caCornerMask: CACornerMask { + switch self { + case .topLeft: + return .layerMinXMinYCorner + case .bottomLeft: + return .layerMinXMaxYCorner + case .topRight: + return .layerMaxXMinYCorner + case .bottomRight: + return .layerMaxXMaxYCorner + default: + return [] + } + } +} diff --git a/Sources/Foundation/AttributedString+Extensions.swift b/Sources/Foundation/AttributedString+Extensions.swift index 7685bf2..106854f 100644 --- a/Sources/Foundation/AttributedString+Extensions.swift +++ b/Sources/Foundation/AttributedString+Extensions.swift @@ -44,8 +44,8 @@ extension NSAttributedString { } extension NSMutableAttributedString { - /// Apply font attribute - func oy_applyFont(_ font: UIFont) { + /// Set font attribute + func oy_set(font: UIFont) { let base = font.fontDescriptor let range = NSRange(location: 0, length: length) beginEditing() @@ -59,4 +59,10 @@ extension NSMutableAttributedString { } endEditing() } + + func oy_set(textAlignment: NSTextAlignment) { + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = textAlignment + addAttribute(.paragraphStyle, value: paragraph, range: NSRange(location: 0, length: length)) + } } diff --git a/Sources/Foundation/Bundle+Extensions.swift b/Sources/Foundation/Bundle+Extensions.swift index 67a360f..69dcb71 100644 --- a/Sources/Foundation/Bundle+Extensions.swift +++ b/Sources/Foundation/Bundle+Extensions.swift @@ -60,6 +60,12 @@ extension Bundle { } task.resume() } + + /// Checks if the app is installed from the app store + /// `Bundle.main.oy_isAppInstalledFromAppStore`→ output → true + public var oy_isAppInstalledFromAppStore: Bool { + return Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" + } /// `Bundle.main.oy_appBuildNumber`→ output → "1" public var oy_appBuildNumber: String? { oy_infoPlistValue(key: "CFBundleVersion") as? String } @@ -98,12 +104,19 @@ extension Bundle { return !applicationClassName.responds(to: Selector(("shared"))) } - /// `Bundle.main.oy_infoPlist(type: [String: Any].self)` - public func oy_infoPlist(type: T.Type = [String: Any].self) -> T? { - oy_read(file: "Info", fileType: "plist", type: type) + /// `Bundle.main.oy_infoPlistAllData` → output → Dictionary + public var oy_infoPlistAllData: [String: Any] { + oy_read(type: [String: Any].self, name: "Info", fileType: "plist") ?? [:] + } + + /// `Bundle.main.oy_read(type: [String: Any].self, name: "Info", fileType: "plist")`→ output → Dictionary + public func oy_read(type: T.Type, name: String, fileType: String) -> T? { + guard let data = oy_data(name, fileType: fileType) else { return nil } + guard let result = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? T else { return nil } + return result } - /// `Bundle.main.oy_infoPlistValue(key: "SecretKey")` + /// `Bundle.main.oy_infoPlistValue(key: "SecretKey")`→ output → "SecretValue" public func oy_infoPlistValue(key: String) -> Any? { object(forInfoDictionaryKey: key) } @@ -115,11 +128,9 @@ extension Bundle { return decoded } - /// `Bundle.main.oy.read(resource: "Info", fileType: "plist", type: Model.self)` - public func oy_read(file: String, fileType: String? = nil, type: T.Type) -> T? { - guard let data = oy_data(file, fileType: fileType) else { return nil } - guard let result = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? T else { return nil } - return result + /// `Bundle.main.oy_path(name: "sample", fileType: "html")` + public func oy_path(_ name: String, fileType: String? = nil) -> URL? { + return url(forResource: name, withExtension: fileType) } /// `Bundle.main.oy_write(resource: "Sample", fileType: "plist", value: newInstance)` @@ -130,10 +141,6 @@ extension Bundle { } extension Bundle { - private func oy_path(_ resource: String, fileType: String? = nil) -> URL? { - return url(forResource: resource, withExtension: fileType) - } - private func oy_data(_ resource: String, fileType: String?) -> Data? { guard let url = oy_path(resource, fileType: fileType) else { return nil } guard let data = try? Data(contentsOf: url) else { return nil } diff --git a/Sources/Foundation/Data+Extensions.swift b/Sources/Foundation/Data+Extensions.swift index d60a6ee..065bde9 100644 --- a/Sources/Foundation/Data+Extensions.swift +++ b/Sources/Foundation/Data+Extensions.swift @@ -15,6 +15,11 @@ extension Data { public func oy_decode(decoder: JSONDecoder = JSONDecoder()) throws -> T { try decoder.decode(T.self, from: self) } + + /// `data.oy_decode(with: nil)` + public func oy_decode(with decoder: JSONDecoder?) -> T? { + try? (decoder ?? JSONDecoder()).decode(T.self, from: self) + } /// `data.oy_string()` public func oy_string(encoding: String.Encoding = .utf8) -> String? { diff --git a/Sources/Foundation/Date+Extensions.swift b/Sources/Foundation/Date+Extensions.swift index 17b4491..9b17fe6 100644 --- a/Sources/Foundation/Date+Extensions.swift +++ b/Sources/Foundation/Date+Extensions.swift @@ -270,10 +270,10 @@ extension Date { } /// date1 is "03.02.2023 12:45" - /// `date1.start(of: .year)` → output → "01.01.2023 00:00:00" - /// `date1.start(of: .month)` → output → "01.02.2023 00:00:00" - /// `date1.start(of: .hour)` → output → "03.02.2023 12:00:00" - /// `date1.start(of: .minute)` → output → "03.02.2023 12:45:00" + /// `date1.oy_start(of: .year)` → output → "01.01.2023 00:00:00" + /// `date1.oy_start(of: .month)` → output → "01.02.2023 00:00:00" + /// `date1.oy_start(of: .hour)` → output → "03.02.2023 12:00:00" + /// `date1.oy_start(of: .minute)` → output → "03.02.2023 12:45:00" public func oy_start(of component: Calendar.Component) -> Date { if component == .day { return Calendar.current.startOfDay(for: self) @@ -295,11 +295,11 @@ extension Date { } /// date1 is "03.02.2023 12:45" - /// `date1.start(of: .year)` → output → "31.12.2023 23:59:59" - /// `date1.start(of: .month)` → output → "29.02.2023 23:59:59" - /// `date1.start(of: .hour)` → output → "03.02.2023 12:59:59" - /// `date1.start(of: .minute)` → output → "03.02.2023 12:45:59" - public func end(of component: Calendar.Component) -> Date { + /// `date1.oy_end(of: .year)` → output → "31.12.2023 23:59:59" + /// `date1.oy_end(of: .month)` → output → "29.02.2023 23:59:59" + /// `date1.oy_end(of: .hour)` → output → "03.02.2023 12:59:59" + /// `date1.oy_end(of: .minute)` → output → "03.02.2023 12:45:59" + public func oy_end(of component: Calendar.Component) -> Date { let date = oy_start(of: component) var components: DateComponents? { switch component { @@ -441,7 +441,7 @@ extension Date { extension String { /// `"17-05-2023".oy_date(format: .short)` → output → (Date) $R0 = 2023-05-17 00:00:00 UTC - public func oy_date(format: OYDateFormat = .default) -> Date { - format.dateFormatter.date(from: self) ?? Date() + public func oy_date(format: OYDateFormat = .default) -> Date? { + format.dateFormatter.date(from: self) } } diff --git a/Sources/Foundation/Decodable+Extensions.swift b/Sources/Foundation/Decodable+Extensions.swift index c0c5955..d882f34 100644 --- a/Sources/Foundation/Decodable+Extensions.swift +++ b/Sources/Foundation/Decodable+Extensions.swift @@ -8,6 +8,22 @@ import Foundation extension Decodable { + /// Init Decodable object with JSON Data + /// + /// let jsonData = """ + /// { + /// "id": 1, + /// "name": "Josefina", + /// "surname": "Calvo" + /// } + /// """.oy_data! + /// + /// let decoded = User(from: jsonData) + public init?(from data: Data, using decoder: JSONDecoder = .init()) { + guard let decoded = try? decoder.decode(Self.self, from: data) else { return nil } + self = decoded + } + /// `ClassSample.oy_className` → output → "ClassSample" public static var oy_className: String { String(describing: self) diff --git a/Sources/Foundation/DispatchQueue+Extensions.swift b/Sources/Foundation/DispatchQueue+Extensions.swift index 67116a1..b5e8788 100644 --- a/Sources/Foundation/DispatchQueue+Extensions.swift +++ b/Sources/Foundation/DispatchQueue+Extensions.swift @@ -20,7 +20,7 @@ extension DispatchQueue { /// `DispatchQueue.oy_asyncOnMain { }` public static func oy_asyncOnMain(_ execute: @escaping @convention(block) () -> Void) { - if pthread_main_np() != 0 { + if oy_isMainQueue { execute() } else { DispatchQueue.main.async(execute: execute) @@ -29,7 +29,7 @@ extension DispatchQueue { /// `DispatchQueue.oy_syncOnMain { }` public static func oy_syncOnMain(_ execute: @escaping @convention(block) () -> Void) { - if pthread_main_np() != 0 { + if oy_isMainQueue { execute() } else { DispatchQueue.main.sync(execute: execute) diff --git a/Sources/Gesture/OYLongPressGesture.swift b/Sources/Gesture/OYLongPressGesture.swift index 89e82aa..cf8c4ee 100644 --- a/Sources/Gesture/OYLongPressGesture.swift +++ b/Sources/Gesture/OYLongPressGesture.swift @@ -24,9 +24,11 @@ import UIKit public final class OYLongPressGesture: UILongPressGestureRecognizer, OYBaseGesture { private var longPressAction: (() -> Void)? - public convenience init(completion: (() -> Void)?) { + public convenience init(minimumPressDuration: CGFloat = 0.3, completion: (() -> Void)?) { self.init() longPressAction = completion + + self.minimumPressDuration = minimumPressDuration addTarget(self, action: #selector(didLongPress(_:))) } diff --git a/Sources/Other/OYSaveImage.swift b/Sources/Other/OYSaveImage.swift new file mode 100644 index 0000000..d223f80 --- /dev/null +++ b/Sources/Other/OYSaveImage.swift @@ -0,0 +1,28 @@ +// +// OYSaveImage.swift +// OYExtensions +// +// Created by osmanyildirim +// + +import Foundation + +struct OYSaveImage { + final class delegate: NSObject { + let completion: ((Error?) -> Void)? + + init(completion: ((Error?) -> Void)?) { + self.completion = completion + } + + @objc func savedImage(_ image: UIImage, error: Error?, context: UnsafeMutableRawPointer?) { + self.completion?(error) + } + } + + #warning("yorum ekle") + func saveImage(image: UIImage, completion: ((Error?) -> Void)?) { + let delegate = delegate(completion: completion) + UIImageWriteToSavedPhotosAlbum(image, delegate, #selector(delegate.savedImage(_: error: context:)), nil) + } +} diff --git a/Sources/SwiftStdlib/Array+Extensions.swift b/Sources/SwiftStdlib/Array+Extensions.swift index 3885561..29b8927 100644 --- a/Sources/SwiftStdlib/Array+Extensions.swift +++ b/Sources/SwiftStdlib/Array+Extensions.swift @@ -16,7 +16,7 @@ extension Array { /// `[1, 2, 3, 4, 1].oy_item(at: 2)` → output → 3 /// `[1, 2, 3, 4, 1].oy_item(at: 9)` → output → nil public func oy_item(at index: Int) -> Element? { - if Int(index) >= count { + guard count > index else { return nil } return self[index] @@ -49,12 +49,12 @@ extension Array { enumerated().filter { indexSet.contains($0.offset) }.map(\.element) } - /// `["a", "b", "c", "d"].prefix(2)` → output → ["a", "b"] + /// `["a", "b", "c", "d"].oy_items(prefix: 2)` → output → ["a", "b"] public func oy_items(prefix: Int) -> [Element] { Array(self.prefix(prefix)) } - /// `["a", "b", "c", "d"].prefix(2)` → output → ["c", "d"] + /// `["a", "b", "c", "d"].oy_items(suffix: 2)` → output → ["c", "d"] public func oy_items(suffix: Int) -> [Element] { Array(self.suffix(suffix)) } @@ -299,6 +299,16 @@ extension Array { let index = Int.random(in: 0.. Self? { + allCases.randomElement(using: &generator) + } +} diff --git a/Sources/SwiftStdlib/String+Extensions.swift b/Sources/SwiftStdlib/String+Extensions.swift index b99f668..18a2960 100644 --- a/Sources/SwiftStdlib/String+Extensions.swift +++ b/Sources/SwiftStdlib/String+Extensions.swift @@ -291,12 +291,22 @@ extension String { NSLocalizedString(self, comment: "") } - /// `"

Sample

".oy_htmlToAttributedString` - public var oy_htmlToAttributedString: NSAttributedString? { - guard let data = data(using: .utf8) else { - return nil + /// `"

Sample

".oy_htmlToAttributedString(font: allCustomFont, textAlignment: .natural)` + public func oy_htmlToAttributedString(font: UIFont? = nil, textAlignment: NSTextAlignment = .natural) -> NSAttributedString? { + guard let data = data(using: .utf8) else { return NSMutableAttributedString() } + do { + let attributedString = try NSMutableAttributedString(data: data, + options: [.documentType: NSAttributedString.DocumentType.html, + .characterEncoding: String.Encoding.utf8.rawValue], + documentAttributes: nil) + guard let font else { return attributedString } + attributedString.oy_set(font: font) + attributedString.oy_set(textAlignment: textAlignment) + + return attributedString + } catch { + return NSMutableAttributedString() } - return try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) } /// `"hello world".oy_upperFirst` → output → "Hello world" @@ -549,23 +559,6 @@ extension String { allSatisfy({ $0.oy_isEmoji }) } - /// Convert string to html - /// `#"

Hello World

"#.oy_convertToHtml(font: UIFont(name: "HelveticaNeue", size: 18))` - public func oy_convertToHtml(font: UIFont? = nil) -> NSMutableAttributedString { - guard let data = data(using: .utf8) else { return NSMutableAttributedString() } - do { - let attributedString = try NSMutableAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: - NSAttributedString.DocumentType.html, - NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue], - documentAttributes: nil) - guard let font else { return attributedString } - attributedString.oy_applyFont(font) - return attributedString - } catch { - return NSMutableAttributedString() - } - } - /// Encrypts plain String with the given key using AES.GCM and returns a base64 encoded encrypted data /// `let key = SymmetricKey(size: .bits256)` /// `try? "Hello World".encrypted(key: SymmetricKey(size: .bits256))` → output → "aDhmrk8N8i6Bmi9jYycRTm+V46B4LKPe8EAOYR/FUEKrLpKYBmQa" @@ -592,4 +585,19 @@ extension String { } return string } + + /// `"Hello World".oy_sha256Value`→ output → "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e" + public var oy_sha256Value: String { + let inputData = Data(self.utf8) + if #available(iOS 13.0, *) { + let hashedData = SHA256.hash(data: inputData) + let hashString = hashedData.compactMap { + String(format: "%02x", $0) + }.joined() + + return hashString + } else { + return self + } + } } diff --git a/Sources/UIKit/UIApplication+Extensions.swift b/Sources/UIKit/UIApplication+Extensions.swift index cf87454..c94704b 100644 --- a/Sources/UIKit/UIApplication+Extensions.swift +++ b/Sources/UIKit/UIApplication+Extensions.swift @@ -17,9 +17,14 @@ import UIKit #endif extension UIApplication { - /// `UIApplication.oy_badgeCount` → output → 2 + /// Get and Set application icon Badge number public static var oy_badgeCount: Int { - UIApplication.shared.applicationIconBadgeNumber + get { + UIApplication.shared.applicationIconBadgeNumber + } + set(value) { + UIApplication.shared.applicationIconBadgeNumber = value + } } /// Request user tracking authorization with a completion handler returning the user's authorization status. @@ -142,10 +147,14 @@ extension UIApplication { /// `UIApplication.oy_keyWindow` → output → public static var oy_keyWindow: UIWindow? { if #available(iOS 13.0, *) { - return UIApplication.shared.connectedScenes.filter { $0.activationState == .foregroundActive } - .first(where: { $0 is UIWindowScene }) - .flatMap({ $0 as? UIWindowScene })?.windows - .first(where: \.isKeyWindow) + let keyWindow = UIApplication.shared.connectedScenes.filter { $0.activationState == .foregroundActive } + .first(where: { $0 is UIWindowScene }) + .flatMap({ $0 as? UIWindowScene })?.windows + .first(where: \.isKeyWindow) + guard let keyWindow else { + return UIApplication.shared.windows.filter { $0.isKeyWindow }.first + } + return keyWindow } else { return UIApplication.shared.keyWindow } @@ -186,20 +195,15 @@ extension UIApplication { /// `UIApplication.oy_topViewController()` → output → private static func oy_topViewController(base: UIViewController? = UIViewController.oy_root) -> UIViewController? { - var topViewController: UIViewController? - - if base == nil { - return oy_topViewController(base: UIViewController.oy_root) - } - if let navigationController = base as? UINavigationController { - topViewController = oy_topViewController(base: navigationController.visibleViewController) + return oy_topViewController(base: navigationController.visibleViewController) } else if let tabBarController = base as? UITabBarController, let selectedViewController = tabBarController.selectedViewController { - topViewController = oy_topViewController(base: selectedViewController) + return oy_topViewController(base: selectedViewController) + } else if let presented = base?.presentedViewController { + return presented } else { - return base?.presentedViewController ?? base + return base } - return topViewController } /// `UIApplication.oy_observeScreenShot { }` @@ -238,6 +242,11 @@ extension UIApplication { closure?(.failure(.iconNotFound)) } } + + /// `UIApplication.oy_resetAppIcon()` + public static func oy_resetAppIcon() { + UIApplication.shared.setAlternateIconName(nil) + } /// `UIApplication.oy_isReachable` → output → true public static var oy_isReachable: Bool { @@ -253,6 +262,12 @@ extension UIApplication { SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) else { return false } return flags.contains(.reachable) && !flags.contains(.connectionRequired) } + + /// Clears the LaunchScreen cache + /// `UIApplication.oy_clearLaunchScreenCache` + func oy_clearLaunchScreenCache() { + try? FileManager.default.removeItem(atPath: NSHomeDirectory() + "/Library/SplashBoard") + } /// `UIApplication.oy_openSafari(url: URL(string: "http://www.apple.com"))` public static func oy_openSafari(url: URL?, entersReaderIfAvailable: Bool = false) throws { diff --git a/Sources/UIKit/UIButton+Extensions.swift b/Sources/UIKit/UIButton+Extensions.swift index 6297149..e7bfb23 100644 --- a/Sources/UIKit/UIButton+Extensions.swift +++ b/Sources/UIKit/UIButton+Extensions.swift @@ -57,6 +57,12 @@ extension UIButton: OYInit { get { titleColor(for: .selected) } set(value) { setTitleColor(value, for: .selected) } } + + /// Get and Set title font for UIButton + public var oy_titleFont: UIFont? { + get { titleLabel?.font } + set(value) { titleLabel?.font = value } + } /// Get and Set background color of `normal` state for UIButton public var oy_backgroundColorForNormal: UIColor? { diff --git a/Sources/UIKit/UIDevice+Extensions.swift b/Sources/UIKit/UIDevice+Extensions.swift index ecb9118..fc45b53 100644 --- a/Sources/UIKit/UIDevice+Extensions.swift +++ b/Sources/UIKit/UIDevice+Extensions.swift @@ -12,6 +12,9 @@ import UIKit #if canImport(AppTrackingTransparency) import AppTrackingTransparency #endif +#if canImport(AudioToolbox) + import AudioToolbox +#endif #if canImport(AVFoundation) import AVFoundation #endif @@ -43,10 +46,15 @@ extension UIDevice { } /// Create and return a brand new unique identifier - /// `UIDevice.oy_uuid` → output → "68FCCB58-23A5-47BC-AA56-E0757F1BDBEA" + /// `UIDevice.oy_uuid` → output → "68FCCB58-23A5-47BC-AA56-E0757F1BDBEA" public static var oy_uuid: String { NSUUID().uuidString } + + /// `UIDevice.oy_globallyUnique` → output → "7009F12B-64FF-4F69-8C5C-E8A1F3F4BD8A-20863-0000055A7CF567F7" + public static var oy_globallyUnique: String { + ProcessInfo.processInfo.globallyUniqueString + } /// `UIDevice.oy_deviceName` → output → "iPhone 14 Pro Max" public static var oy_deviceName: String { @@ -129,6 +137,7 @@ extension UIDevice { } /// On/Off device's torch (camera flash) + /// `UIDevice.oy_toggleTorch()` public static func oy_toggleTorch(level: Float = 1.0) { guard let device = AVCaptureDevice.default(for: .video), device.hasTorch else { return } @@ -142,6 +151,34 @@ extension UIDevice { device.unlockForConfiguration() } + + /// Make a vibration of device + /// `UIDevice.oy_vibrateDevice()` + public static func oy_vibrateDevice() { + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) + } + + /// `UIDevice.oy_userInterfaceStyle` → output → .dark + @available(iOS 13.0, *) + public static var oy_userInterfaceStyle: UIUserInterfaceStyle { + UITraitCollection.current.userInterfaceStyle + } + + /// `UIDevice.oy_isDarkMode` → output → true + @available(iOS 13.0, *) + public static var oy_isDarkMode: Bool { + UITraitCollection.current.userInterfaceStyle == .dark + } + + /// `UIDevice.oy_isPortrait` → output → true + public static var oy_isPortrait: Bool { + UIDevice.current.orientation.isPortrait + } + + /// `UIDevice.oy_isLandscape` → output → false + public static var oy_isLandscape: Bool { + UIDevice.current.orientation.isLandscape + } } extension UIDevice { @@ -161,6 +198,7 @@ extension UIDevice { case "iPhone7,1": return "iPhone 6 Plus" case "iPhone8,1": return "iPhone 6s" case "iPhone8,2": return "iPhone 6s Plus" + case "iPhone8,4": return "iPhone SE" case "iPhone9,1", "iPhone9,3": return "iPhone 7" case "iPhone9,2", "iPhone9,4": return "iPhone 7 Plus" case "iPhone10,1", "iPhone10,4": return "iPhone 8" @@ -176,17 +214,20 @@ extension UIDevice { case "iPhone13,2": return "iPhone 12" case "iPhone13,3": return "iPhone 12 Pro" case "iPhone13,4": return "iPhone 12 Pro Max" - case "iPhone14,4": return "iPhone 13 mini" - case "iPhone14,5": return "iPhone 13" + case "iPhone12,8": return "iPhone SE (2nd generation)" case "iPhone14,2": return "iPhone 13 Pro" case "iPhone14,3": return "iPhone 13 Pro Max" + case "iPhone14,4": return "iPhone 13 mini" + case "iPhone14,5": return "iPhone 13" + case "iPhone14,6": return "iPhone SE (3rd generation)" case "iPhone14,7": return "iPhone 14" case "iPhone14,8": return "iPhone 14 Plus" case "iPhone15,2": return "iPhone 14 Pro" case "iPhone15,3": return "iPhone 14 Pro Max" - case "iPhone8,4": return "iPhone SE" - case "iPhone12,8": return "iPhone SE (2nd generation)" - case "iPhone14,6": return "iPhone SE (3rd generation)" + case "iPhone15,4": return "iPhone 15" + case "iPhone15,5": return "iPhone 15 Plus" + case "iPhone16,1": return "iPhone 15 Pro" + case "iPhone16,2": return "iPhone 15 Pro Max" case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return "iPad 2" case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad (3rd generation)" case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad (4th generation)" @@ -216,20 +257,55 @@ extension UIDevice { 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 "AppleTV5,3": return "Apple TV" - case "AppleTV6,2": return "Apple TV 4K" + case "iPad14,3", "iPad14,4": return "iPad Pro 11-inch (4th generation)" + case "iPad14,5", "iPad14,6": return "iPad Pro 12.9-inch (6th generation)" case "AudioAccessory1,1": return "HomePod" case "AudioAccessory5,1": return "HomePod mini" case "i386", "x86_64", "arm64": return "Simulator \(oy_deviceModel(ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))" default: return identifier } + #elseif os(watchOS) + switch identifier { + case "Watch1,1": return "Apple Watch (1st generation) - 38mm" + case "Watch1,2": return "Apple Watch (1st generation) - 42mm" + case "Watch2,6": return "Apple Watch Series 1 - 38mm" + case "Watch2,7": return "Apple Watch Series 1 - 42mm" + case "Watch2,3": return "Apple Watch Series 2 - 38mm" + case "Watch2,4": return "Apple Watch Series 2 - 42mm" + case "Watch3,1", "Watch3,3": return "Apple Watch Series 3 - 38mm" + case "Watch3,2", "Watch3,4": return "Apple Watch Series 1 - 42mm" + case "Watch4,1", "Watch4,3": return "Apple Watch Series 4 - 40mm" + case "Watch4,2", "Watch4,4": return "Apple Watch Series 4 - 44mm" + case "Watch5,1", "Watch5,3": return "Apple Watch Series 5 - 40mm" + case "Watch5,2", "Watch5,4": return "Apple Watch Series 5 - 44mm" + case "Watch6,1", "Watch6,3": return "Apple Watch Series 6 - 40mm" + case "Watch6,2", "Watch6,4": return "Apple Watch Series 6 - 44mm" + case "Watch5,9", "Watch5,11": return "Apple Watch SE - 40mm" + case "Watch5,10", "Watch5,12": return "Apple Watch SE - 44mm" + case "Watch6,6", "Watch6,8": return "Apple Watch Series 7 - 41mm" + case "Watch6,7", "Watch6,9": return "Apple Watch Series 7 - 45mm" + case "Watch6,14", "Watch6,16": return "Apple Watch Series 8 - 41mm" + case "Watch6,15", "Watch6,17": return "Apple Watch Series 8 - 45mm" + case "Watch6,10", "Watch6,12": return "Apple Watch SE (2nd generation) - 40mm" + case "Watch6,11", "Watch6,13": return "Apple Watch SE (2nd generation) - 44mm" + case "Watch6,18": return "Apple Watch Ultra" + case "Watch7,3": return "Apple Watch Series 9 - 41mm" + case "Watch7,4": return "Apple Watch Series 9 - 45mm" + case "Watch7,5": return "Apple Watch Ultra2" + case "i386", "x86_64": return "Simulator \(oy_deviceModel(ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "watchOS"))" + default: return identifier + } #elseif os(tvOS) switch identifier { - case "AppleTV5,3": return "Apple TV 4" + case "AppleTV5,3": return "Apple TV" case "AppleTV6,2": return "Apple TV 4K" + case "AppleTV11,1": return "Apple TV 4K (2nd generation)" + case "AppleTV14,1": return "Apple TV 4K (3rd generation" case "i386", "x86_64": return "Simulator \(oy_deviceModel(ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))" default: return identifier } + #elseif os(visionOS) + return identifier #endif } } diff --git a/Sources/UIKit/UIImage+Extensions.swift b/Sources/UIKit/UIImage+Extensions.swift index 032b98d..33e7b75 100644 --- a/Sources/UIKit/UIImage+Extensions.swift +++ b/Sources/UIKit/UIImage+Extensions.swift @@ -69,7 +69,16 @@ extension UIImage { completion?(.success(image)) }.resume() } - + + /// Save image to photo library + public func oy_savePhotoLibrary(completion: ((Error?) -> Void)?) { + guard Bundle.main.object(forInfoDictionaryKey: "NSPhotoLibraryAddUsageDescription") != nil else { + completion?(OYError.permissionError) + return + } + OYSaveImage().saveImage(image: self, completion: completion) + } + /// `image.oy_sizeOnDisk` → output → "1.2578125KB" public var oy_sizeOnDisk: String? { guard let data = jpegData(compressionQuality: 1.0) else { return nil } @@ -97,4 +106,18 @@ extension UIImage { public func oy_compressed(quality: CGFloat = 0.5) -> Data? { jpegData(compressionQuality: quality) } + + /// Apply blur filter to UIImage + /// - Parameter radius: blur radius e.g `50` + /// - Returns: blurred UIImage + public func oy_blurred(radius: CGFloat) -> UIImage { + guard let cgImage = cgImage, let ciFilter = CIFilter(name: "CIGaussianBlur") else { return self } + + let input = CIImage(cgImage: cgImage) + ciFilter.setValue(input, forKey: "inputImage") + ciFilter.setValue(radius, forKey: "inputRadius") + + guard let outputImage = ciFilter.outputImage else { return self } + return UIImage(ciImage: outputImage) + } } diff --git a/Sources/UIKit/UIImageView+Extensions.swift b/Sources/UIKit/UIImageView+Extensions.swift index 93a58a6..47238df 100644 --- a/Sources/UIKit/UIImageView+Extensions.swift +++ b/Sources/UIKit/UIImageView+Extensions.swift @@ -107,4 +107,14 @@ extension UIImageView: OYInit { addSubview(blurEffectView) clipsToBounds = true } + + /// Set image with animation + /// - Parameters: + /// - image: image + /// - duration: animation duration (default is 0.3) + public func oy_setImageAnimate(image: UIImage, duration: TimeInterval = 0.3) { + UIView.transition(with: self, duration: duration, options: .transitionCrossDissolve, animations: { + self.image = image + }, completion: nil) + } } diff --git a/Sources/UIKit/UILabel+Extensions.swift b/Sources/UIKit/UILabel+Extensions.swift index bd27426..3eb806c 100644 --- a/Sources/UIKit/UILabel+Extensions.swift +++ b/Sources/UIKit/UILabel+Extensions.swift @@ -17,12 +17,18 @@ extension UILabel: OYInit { return label } + /// Get and Set text color of UILabel + public var oy_textColor: UIColor? { + get { textColor } + set(value) { textColor = value } + } + /// Set text with animation /// - Parameters: /// - text: Text /// - animated: if the should be animated /// - duration: animation duration - public func oy_setText(_ text: String, animated: Bool = true, duration: TimeInterval = 0.5) { + public func oy_set(text: String?, animated: Bool = true, duration: TimeInterval = 0.5) { self.text = text guard animated else { return } @@ -33,6 +39,18 @@ extension UILabel: OYInit { animation.duration = duration layer.add(animation, forKey: CATransitionType.fade.rawValue) } + + /// Get and Set font of UILabel + public var oy_font: UIFont? { + get { font } + set(value) { font = value } + } + + /// Get and Set text alignment of UILabel + public var oy_textAlignment: NSTextAlignment { + get { textAlignment } + set(value) { textAlignment = value } + } /// Estimated size of UILabel public func oy_estimatedSize(width: CGFloat = .greatestFiniteMagnitude, height: CGFloat = .greatestFiniteMagnitude) -> CGSize { @@ -85,4 +103,16 @@ extension UILabel: OYInit { attributedText = attributedString } } + + /// Spaces between lines + /// - Parameter linesSpace: space value + public func oy_set(linesSpace: CGFloat) { + guard let text else { return } + let textAlignment = textAlignment + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = linesSpace + let attributedString = NSAttributedString(string: text, attributes: [.paragraphStyle: paragraphStyle]) + self.attributedText = attributedString + self.textAlignment = textAlignment + } } diff --git a/Sources/UIKit/UINavigationBar+Extensions.swift b/Sources/UIKit/UINavigationBar+Extensions.swift index c6ef888..db66e7a 100644 --- a/Sources/UIKit/UINavigationBar+Extensions.swift +++ b/Sources/UIKit/UINavigationBar+Extensions.swift @@ -28,7 +28,7 @@ extension UINavigationBar { } /// Get and Set background color - public var oy_backgroundColor: UIColor? { + public var oy_background: UIColor? { get { backgroundColor } set(value) { diff --git a/Sources/UIKit/UIScreen+Extensions.swift b/Sources/UIKit/UIScreen+Extensions.swift index 72a6409..a92f07a 100644 --- a/Sources/UIKit/UIScreen+Extensions.swift +++ b/Sources/UIKit/UIScreen+Extensions.swift @@ -47,6 +47,11 @@ extension UIScreen { public static var oy_isRetinaHD: Bool { main.responds(to: #selector(displayLink(withTarget: selector:))) && main.scale == 3.0 } + + /// `UIScreen.oy_screenBrightness` → output → 0.5 + public static var oy_screenBrightness: CGFloat { + UIScreen.main.brightness + } /// `UIScreen.oy_adjustBrightness(value: 0.5)` public static func oy_adjustBrightness(value: CGFloat) { diff --git a/Sources/UIKit/UIScrollView+Extensions.swift b/Sources/UIKit/UIScrollView+Extensions.swift index 6c10fba..7871c1a 100644 --- a/Sources/UIKit/UIScrollView+Extensions.swift +++ b/Sources/UIKit/UIScrollView+Extensions.swift @@ -37,6 +37,37 @@ extension UIScrollView { get { contentOffset.y } set(value) { setContentOffset(.oy_init(oy_contentOffsetX, value), animated: true) } } + + /// Get and Set contentInset of UIScrollView + public var oy_contentInset: UIEdgeInsets { + get { contentInset } + set(value) { contentInset = value } + } + + /// Get and Set contentInset Top of UIScrollView + public var oy_contentInsetTop: CGFloat { + get { contentInset.top } + set(value) { + contentInset = .oy_init(value, oy_contentInsetLeft, oy_contentInsetBottom, oy_contentInsetRight) } + } + + /// Get and Set contentInset Bottom of UIScrollView + public var oy_contentInsetBottom: CGFloat { + get { contentInset.bottom } + set(value) { contentInset = .oy_init(oy_contentInsetTop, oy_contentInsetLeft, value, oy_contentInsetRight) } + } + + /// Get and Set contentInset Left of UIScrollView + public var oy_contentInsetLeft: CGFloat { + get { contentInset.left } + set(value) { contentInset = .oy_init(oy_contentInsetTop, value, oy_contentInsetBottom, oy_contentInsetRight) } + } + + /// Get and Set contentInset Right of UIScrollView + public var oy_contentInsetRight: CGFloat { + get { contentInset.right } + set(value) { contentInset = .oy_init(oy_contentInsetTop, oy_contentInsetLeft, oy_contentInsetBottom, value) } + } /// Set UIScrollView contentOffset /// - Parameters: diff --git a/Sources/UIKit/UISearchBar+Extensions.swift b/Sources/UIKit/UISearchBar+Extensions.swift index d39c84c..30aa098 100644 --- a/Sources/UIKit/UISearchBar+Extensions.swift +++ b/Sources/UIKit/UISearchBar+Extensions.swift @@ -22,6 +22,11 @@ extension UISearchBar: OYInit { return textField } + /// Get UISearchBar's optional Clear Button + public var oy_clearButton: UIButton? { + oy_textField?.value(forKey: "clearButton") as? UIButton + } + /// Clear UISearchBar's text public func oy_clear() { text?.removeAll() diff --git a/Sources/UIKit/UITableView+Extensions.swift b/Sources/UIKit/UITableView+Extensions.swift index 6ee80f9..c5eb6d9 100644 --- a/Sources/UIKit/UITableView+Extensions.swift +++ b/Sources/UIKit/UITableView+Extensions.swift @@ -152,8 +152,8 @@ extension UITableView: OYInit { /// Top and Bottom round corners of UITableView /// - Parameter radius: corner radius size public func oy_roundCornersTopAndBottom(_ radius: CGFloat) { - cellForRow(at: IndexPath(row: 0, section: 0))?.oy_cornerRadius(corners: [.topLeft, .topRight], radius: radius) - cellForRow(at: IndexPath(row: numberOfRows(inSection: 0) - 1, section: 0))?.oy_cornerRadius(corners: [.bottomLeft, .bottomRight], radius: radius) + cellForRow(at: IndexPath(row: 0, section: 0))?.oy_cornerRadius(corners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], radius: radius) + cellForRow(at: IndexPath(row: numberOfRows(inSection: 0) - 1, section: 0))?.oy_cornerRadius(corners: [.layerMinXMaxYCorner, .layerMaxXMaxYCorner], radius: radius) } /// Get and Set refreshControl for UITableView diff --git a/Sources/UIKit/UITextField+Extensions.swift b/Sources/UIKit/UITextField+Extensions.swift index d8c078c..3dad607 100644 --- a/Sources/UIKit/UITextField+Extensions.swift +++ b/Sources/UIKit/UITextField+Extensions.swift @@ -80,7 +80,7 @@ extension UITextField: OYInit { /// Returns replaced string of UITextField with entered text /// Should be used within the `textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String)` method /// `let text = textField.shouldText(with: range, replacementString: string)` - func oy_shouldText(with range: NSRange, replacementString string: String) -> String { + public func oy_shouldText(with range: NSRange, replacementString string: String) -> String { guard let text = text else { return "" } return NSString(string: text).replacingCharacters(in: range, with: string) } diff --git a/Sources/UIKit/UIView+Extensions.swift b/Sources/UIKit/UIView+Extensions.swift index 8a27701..80b41c0 100644 --- a/Sources/UIKit/UIView+Extensions.swift +++ b/Sources/UIKit/UIView+Extensions.swift @@ -50,10 +50,8 @@ extension UIView { } /// Hide UIView with animation - public func oy_hide(animated: Bool = false) { - UIView.animate(withDuration: animated ? 0.5 : 0.0) { - self.alpha = 0 - } + public func oy_hide() { + isHidden = true } /// Determines whether user events are ignored and removed from the event queue @@ -92,6 +90,11 @@ extension UIView { public func oy_addSubviews(_ subviews: UIView...) { _ = subviews.map({ addSubview($0) }) } + + /// Insert of subview with index + public func oy_insertSubview(_ subview: UIView, index: Int) { + insertSubview(subview, at: index) + } /// Remove subviews of UIView public func oy_removeSubviews() { @@ -112,34 +115,39 @@ extension UIView { } /// Snapshot of UIView - @objc var oy_snapshot: UIImage? { - UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, 0) - defer { - UIGraphicsEndImageContext() + @objc public var oy_snapshot: UIImage? { + let renderer = UIGraphicsImageRenderer(bounds: bounds) + return renderer.image { rendererContext in + layer.render(in: rendererContext.cgContext) } - guard let context = UIGraphicsGetCurrentContext() else { return nil } - layer.render(in: context) - return UIGraphicsGetImageFromCurrentImageContext() + } + + /// Get and Set background color of UIView + public var oy_backgroundColor: UIColor? { + get { backgroundColor } + set(value) { backgroundColor = value } } /// Circle UIView public func oy_circle() { layer.cornerRadius = frame.height / 2 + layer.masksToBounds = true } /// Set corner radius of view - public func oy_cornerRadius(corners: UIRectCorner = .allCorners, radius: CGFloat) { - layoutIfNeeded() - let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) - let mask = CAShapeLayer() - mask.path = path.cgPath - layer.mask = mask + public func oy_cornerRadius(corners: CACornerMask = [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner], radius: CGFloat) { + layer.cornerRadius = radius + layer.masksToBounds = true + layer.maskedCorners = corners } /// Add border to UIView - public func oy_addBorder(color: UIColor, width: CGFloat) { + public func oy_addBorder(color: UIColor, width: CGFloat, radius: CGFloat? = nil) { layer.borderWidth = width layer.borderColor = color.cgColor + layer.masksToBounds = true + guard let radius else { return } + layer.cornerRadius = radius } /// Remove UIView's border @@ -149,6 +157,21 @@ extension UIView { layer.borderColor = nil layer.masksToBounds = maskToBounds } + + /// Update UIView's border with animation + public func oy_updateBorderAnimate(color: UIColor?, width: CGFloat?, radius: CGFloat? = nil, duration: CGFloat = 0.5) { + UIViewPropertyAnimator(duration: duration, curve: .easeIn) { + if let color { + self.layer.borderColor = color.cgColor + } + if let width { + self.layer.borderWidth = width + } + if let radius { + self.layer.cornerRadius = radius + } + }.startAnimation() + } /// Add dashed border to UIView public func oy_dashedBorder(color: UIColor, width: CGFloat, radius: CGFloat) { @@ -168,7 +191,7 @@ extension UIView { } /// Get and Set cornerRadius for UIView - var oy_cornerRadius: CGFloat { + public var oy_cornerRadius: CGFloat { get { return layer.cornerRadius } @@ -179,7 +202,7 @@ extension UIView { } /// Get and Set borderWidth for UIView - var oy_borderWidth: CGFloat { + public var oy_borderWidth: CGFloat { get { return layer.borderWidth } @@ -189,7 +212,7 @@ extension UIView { } /// Get and Set borderColor for UIView - var oy_borderColor: UIColor? { + public var oy_borderColor: UIColor? { get { if let borderColor = layer.borderColor { return UIColor(cgColor: borderColor) @@ -201,6 +224,28 @@ extension UIView { layer.borderColor = newValue?.cgColor } } + + /// Global point on UIWindow of UIView + public var oy_globalPoint: CGPoint? { + guard let window = UIApplication.oy_keyWindow else { + return nil + } + return window.convert(frame.origin, to: nil) + } + + /// Global frame on UIWindow of UIView + public var oy_globalFrame: CGRect? { + guard let window = UIApplication.oy_keyWindow else { + return nil + } + return convert(bounds, to: window) + } + + /// Impact between UI elements + public func oy_impact(_ style: UIImpactFeedbackGenerator.FeedbackStyle = .light) { + let feedbackGenerator = UIImpactFeedbackGenerator(style: style) + feedbackGenerator.impactOccurred() + } /// Add UITapGestureRecognizer to UIView with completion handler public func oy_didTap(tapCount: Int = 1, touchCount: Int = 1, _ completion: ((UIView) -> Void)?) { @@ -309,17 +354,34 @@ extension UIView { layer.insertSublayer(gradient, at: 0) } + /// Make clear hole with corner radius on UIView + public func oy_makeHole(frame: CGRect, cornerRadius: CGFloat) { + let maskLayer = CAShapeLayer() + maskLayer.fillRule = .evenOdd + maskLayer.fillColor = UIColor.black.cgColor + + let pathToOverlay = UIBezierPath(rect: bounds) + pathToOverlay.append(UIBezierPath(roundedRect: frame, cornerRadius: cornerRadius)) + pathToOverlay.usesEvenOddFillRule = true + maskLayer.path = pathToOverlay.cgPath + + layer.mask = maskLayer + } + // MARK: - Animations /// Start `FadeIn` animation with duration - public func oy_fadeInAnimation(duration: TimeInterval = 0.5) { - alpha = 0 + public func oy_fadeIn(duration: TimeInterval = 0.5, skipSetAlphaZero: Bool = false) { + if !skipSetAlphaZero { + alpha = 0 + } + UIView.animate(withDuration: duration) { self.alpha = 1 } } /// Start `FadeOut` animation with duration - public func oy_fadeOutAnimation(duration: TimeInterval = 0.5) { + public func oy_fadeOut(duration: TimeInterval = 0.5) { UIView.animate(withDuration: duration) { self.alpha = 0 } diff --git a/Sources/UIKit/UIViewController+Extensions.swift b/Sources/UIKit/UIViewController+Extensions.swift index c9f509f..b36f309 100644 --- a/Sources/UIKit/UIViewController+Extensions.swift +++ b/Sources/UIKit/UIViewController+Extensions.swift @@ -28,12 +28,29 @@ extension UIViewController { /// // do stuff /// } public func oy_present(viewControler: UIViewController, transitionStyle: UIModalTransitionStyle? = nil, completion: (() -> Void)? = nil) { - if let transitionStyle = transitionStyle { + if let transitionStyle { viewControler.modalTransitionStyle = transitionStyle } present(viewControler, animated: true, completion: completion) } + /// `self.oy_present(viewControler: SecondViewController(), presentationStyle: .fullScreen) { + /// // do stuff + /// } + public func oy_present(viewControler: UIViewController, presentationStyle: UIModalPresentationStyle? = nil, onIpad: Bool = false, completion: (() -> Void)? = nil) { + if let presentationStyle { + viewControler.modalPresentationStyle = presentationStyle + } + + guard #available(iOS 13.0, *), viewControler.modalPresentationStyle == .formSheet, onIpad else { + present(viewControler, animated: true, completion: completion) + return + } + + viewControler.preferredContentSize = .init(width: UIScreen.oy_width, height: UIScreen.oy_height - 44) + present(viewControler, animated: true, completion: completion) + } + /// Push ViewController with completion handler @objc public func oy_push(viewControler: UIViewController, animated: Bool = true, completion: (() -> Void)? = nil) { navigationController?.oy_push(viewControler: viewControler, animated: animated, completion: completion) diff --git a/SwiftUI/UIView+Preview.swift b/SwiftUI/UIView+Preview.swift new file mode 100644 index 0000000..cd4fae8 --- /dev/null +++ b/SwiftUI/UIView+Preview.swift @@ -0,0 +1,34 @@ +// +// UIViewPreview.swift +// OYExtensions +// +// Created by osmanyildirim +// + +import SwiftUI + +/// Preview the UIView design on the Xcode canvas. +/// +/// struct View_Preview: PreviewProvider { +/// static var previews: some View { +/// UIViewControllerPreview(FirstView()) +/// } +/// } + +@available(iOS 13.0, *) +public struct UIViewPreview: UIViewRepresentable { + private let view: V + + public init(_ view: V) { + self.view = view + } + + public func makeUIView(context: Context) -> UIView { + return view + } + + public func updateUIView(_ view: UIView, context: Context) { + view.setContentHuggingPriority(.defaultHigh, for: .horizontal) + view.setContentHuggingPriority(.defaultHigh, for: .vertical) + } +} diff --git a/SwiftUI/UIViewController+Preview.swift b/SwiftUI/UIViewController+Preview.swift new file mode 100644 index 0000000..af41923 --- /dev/null +++ b/SwiftUI/UIViewController+Preview.swift @@ -0,0 +1,31 @@ +// +// UIViewControllerPreview.swift +// OYExtensions +// +// Created by osmanyildirim +// + +import SwiftUI + +/// Preview the UIViewController design on the Xcode canvas. +/// +/// struct ViewController_Preview: PreviewProvider { +/// static var previews: some View { +/// UIViewControllerPreview(FirstViewController()) +/// } +/// } + +@available(iOS 13.0, *) +public struct UIViewControllerPreview: UIViewControllerRepresentable { + private let viewController: V + + public init(_ viewController: V) { + self.viewController = viewController + } + + public func makeUIViewController(context: Context) -> V { + viewController + } + + public func updateUIViewController(_ uiViewController: V, context: Context) { } +}