VCore is a Swift collection containing objects, functions, and extensions that I use for my projects.
Package files are grouped as:
Services and Managers. Services, managers, controllers, and formatters. For instance, NetworkClient
.
Views. Reusable non-scene View
s, UIView
s, and UIViewController
s. For instance BaseButton
s.
Models. Reusable models. For instance, EdgeInsets
s.
Helpers. Non-service, non-extension objects and methods. For instance, architectural pattern helpers.
Extensions. Global extensions. Methods and properties are grouped by frameworks of origin—Core Frameworks
, Foundation
, SwiftUI
, UIKit
, and AppKit
(only for supporting underlying SwiftUI
types).
Global Functions. Global functions. For instance, TODO
.
API. Objects used for interfacing from you app/package to VCore
. For instance, VCoreLocalizationManager
.
Package incudes folder Extra
, which contains:
Project includes folder Documentation
, which contains:
Swift style guide
Documentation and an example app of UIKit VIPER architecture
Documentation and an example app of SwiftUI VIPER architecture
Documentation of CLEAN Interactors and Gateways
NetworkClient
with customizable requests, responses, and return types:
do {
var request: NetworkRequest = .init(url: "https://httpbin.org/post")
request.method = .POST
try request.addHeaders(encodable: JSONRequestHeaders())
try request.addBody(json: ["key": "value"])
let result: [String: Any?] = try await NetworkClient.default.json(from: request)
print(result)
} catch {
print(error.localizedDescription)
}
MultipartFormDataBuilder
with a Dictionary
-based file API:
do {
let json: [String: Any?] = [
"key": "value"
]
let files: [String: (some AnyMultipartFormDataFile)?] = [
"profile": MultipartFormDataFile(
mimeType: "image/jpeg",
data: profileImage?.jpegData(compressionQuality: 0.25)
),
"gallery": galleryImages?.enumerated().compactMap { (index, image) in
MultipartFormDataFile(
filename: "IMG_\(index).jpg",
mimeType: "image/jpeg",
data: image?.jpegData(compressionQuality: 0.25)
)
}
]
let (boundary, data): (String, Data) = try MultipartFormDataBuilder().build(
json: json,
files: files
)
var request: NetworkRequest = .init(url: "https://somewebsite.com/api/some_endpoint")
request.method = .POST
try request.addHeaders(encodable: MultipartFormDataAuthorizedRequestHeaders(
boundary: boundary,
token: "token"
))
request.addBody(data: data)
let result: [String: Any?] = try await NetworkClient.default.json(from: request)
print(result)
} catch {
print(error.localizedDescription)
}
LocalizationManager
that manages localizations without interacting with raw String
s:
extension Locale {
static var english: Self { .init(identifier: "en") }
static var english_uk: Self { .init(identifier: "en-GB") }
static var spanish: Self { .init(identifier: "es") }
}
LocalizationManager.shared.addLocales([.english, .english_uk, .spanish])
LocalizationManager.shared.setDefaultLocale(to: .english)
LocalizationManager.shared.setCurrentLocale(to: .english)
let lhs: Locale = .init(identifier: "en")
let rhs: Locale = .init(identifier: "en-US")
lhs == rhs // false
lhs.isEquivalent(to: rhs) // true, if `Locale.current.regionCode` is "US"
KeychainService
that supports custom queries, and has a dedicated property wrapper:
KeychainService.default.get(key: "SomeKey")
KeychainService.default.set(key: "SomeKey", data: data)
KeychainService.default.delete(key: "SomeKey")
@KeychainStorage("AccessToken") var accessToken: String?
KeyPathInitializableEnumeration
that allows for initialization of an enum
with a KeyPath
:
enum SomeEnum: KeyPathInitializableEnumeration {
case first
case second
var someProperty: Int {
switch self {
case .first: return 1
case .second: return 2
}
}
}
let value: SomeEnum? = .aCase(key: \.someProperty, value: 2)
DigitalTimeFormatter
with various configurations:
let formatter: DigitalTimeFormatter = .init()
formatter.string(from: 905048) // "10:11:24:08"
KeyboardResponsiveUIViewController
that handles keyboard notifications:
final class ViewController: KeyboardResponsiveUIViewController {
private let textField: UITextField = { ... }()
override func viewDidLoad() {
super.viewDidLoad()
view.addsupview(textField)
NSLayoutConstraint.activate([
...
])
}
override func keyboardWillShow(_ systemKeyboardInfo: SystemKeyboardInfo) {
super.keyboardWillShow(systemKeyboardInfo)
UIView.animateKeyboardResponsiveness(
systemKeyboardInfo: systemKeyboardInfo,
animations: { [weak self] in
guard let self else { return }
view.bounds.origin.y = -systemKeyboardInfo.frame.size.height
view.superview?.layoutIfNeeded()
}
)
}
override func keyboardWillHide(_ systemKeyboardInfo: SystemKeyboardInfo) {
super.keyboardWillHide(systemKeyboardInfo)
UIView.animateKeyboardResponsiveness(
systemKeyboardInfo: systemKeyboardInfo,
animations: { [weak self] in
guard let self else { return }
view.bounds.origin.y = 0
view.superview?.layoutIfNeeded()
}
)
}
}
FetchDelegatingAsyncImage
that asynchronously loads and displays an Image
with a delegated fetch handler.
You can customize request with access token and headers, implement custom caching, and more.
var body: some View {
FetchDelegatingAsyncImage(
from: URL(string: "https://somewebsite.com/content/image.jpg")!,
fetch: fetchImage,
content: { phase in
if let image = phase.image {
image
.resizable()
.fitToAspect(1, contentMode: .fill)
} else if phase.error != nil {
ErrorView()
} else {
ProgressView()
}
}
)
.frame(dimension: 200)
}
...
private var cache: NSCache<NSString, UIImage> = .init()
private func fetchImage(url: URL) async throws -> Image {
let key: NSString = .init(string: url.absoluteString)
switch cache.object(forKey: key) {
case nil:
var request: NetworkRequest = .init(url: url)
try request.addHeaders(encodable: JSONAuthorizedRequestHeaders(token: "token"))
let data: Data = try await NetworkClient.default.data(from: request)
guard let uiImage: UIImage = .init(data: data) else { throw NetworkClientError.invalidData }
cache.setObject(uiImage, forKey: key)
return Image(uiImage: uiImage)
case let uiImage?:
return Image(uiImage: uiImage)
}
}
Optional
comparison:
let a: Int? = 10
let b: Int? = nil
a.isOptionalLess(than: b, order: .nilIsLess) // false
a.isOptionalLess(than: b, order: .nilIsGreater) // true
Detecting changes in View
size:
@State private var size: CGSize = .zero
var body: some View {
VStack(content: {
Color.accentColor
.onSizeChange(perform: { size = $0 })
})
}
Add https://github.com/VakhoKontridze/VCore
as a Swift Package in Xcode and follow the instructions.
Package provides limited macOS
, tvOS
, and watchOS
support.
Versions with different majors are not directly compatible. When a new major is released, deprecated symbols are removed.
Major. Major changes, such as big overhauls
Minor. Minor changes, such as new objects, functions, and extensions
Patch. Bug fixes and improvements
Version | Release Date | Swift | SDK | Comment |
---|---|---|---|---|
4.0 (4.0.0 - 4.x.x) |
2022 09 14 |
5.8 (4.7.0 - 4.x.x) 5.7 (4.0.0 - 4.6.1) |
iOS 13.0 macOS 10.15 tvOS 13.0 watchOS 6.0 |
API changes. |
3.0 (3.0.0 - 3.20.2) |
2022 05 17 | 5.6 |
iOS 13.0 macOS 10.15 tvOS 13.0 watchOS 6.0 |
Multiplatform support. SPM support. |
2.0 (2.0.0 - 2.3.0) |
2021 12 28 | 5.3 | iOS 13.0 | iOS 13.0 support |
1.0 (1.0.0 - 1.4.0) |
2021 10 07 | 5.3 | iOS 14.0 | - |
For additional info, refer to the CHANGELOG.
e-mail: [email protected]
link |
Stars: 42 |
Last commit: 17 hours ago |
General
mac
Catalyst
Views
ResponderChainToolBar
, alongside with ResponderChainToolBarManager
is added, that handles focus navigation in the responder chainView.responderChainToolBar(focus:_)
method is added that manages focus navigation in the responder chainInnerShadowUIView
members now have public
/open
access levelExtensions
Array.binaryAppend(_:by:)
method is added that inserts elements in appropriate placeArray.asyncSorted(by:)
and Array.asyncSort(by:)
methods are added that asynchronously sorts an Array
Collection.firstElement(ofType:where:)
and Collection.lastElement(ofType:where:)
methods are addedCollection.firstIndex(ofType:where:)
and Collection.lastIndex(ofType:where:)
methods are addedCollection.lastIndex(ofType:where:)
and Collection.lastIndex(ofType:where:)
methods are addedBidirectionalCollection.lastIndexAndElement(where:)
method is added that complements similar method that seeks first index and elementCaseIterable.aCase(offseteBy:)
method is added that returns a case
with specified distance from the current case
. CaseIterable.previousCase
and CaseIterable.nextCase
properties are also added.Task.sleep(seconds:)
method is added that takes TimeInterval
as a parameterColor.init?(hex:)
method is added that creates Color
from a hex UInt64
or String
UIColor.init?(hex:)
and UIColor.init?(displayP3Hex:)
methods is added that creates UIColor
from a hex UInt64
or String
Array.subscript(safe:)
now supports Collection
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics