Build iOS App from scratch - Part 1 - Create Swift Package
Build iOS App from scratch - Part 2 - Clean Code with SwiftLint
Build iOS App from scratch - Part 3 - Reusable Logger in Swift
import SwiftyBeaver
protocol BGLoggerType {
func verbose(_ message: String, _ file: String, _ function: String, _ line: Int)
func debug(_ message: String, _ file: String, _ function: String, _ line: Int)
func info(_ message: String, _ file: String, _ function: String, _ line: Int)
func warning(_ message: String, _ file: String, _ function: String, _ line: Int)
func error(_ message: String, _ file: String, _ function: String, _ line: Int)
}
extension BGLoggerType {
func verbose(_ message: String, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
verbose(message, file, function, line)
}
func debug(_ message: String, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
debug(message, file, function, line)
}
func info(_ message: String, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
info(message, file, function, line)
}
func warning(_ message: String, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
warning(message, file, function, line)
}
func error(_ message: String, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
error(message, file, function, line)
}
}
class BGLogger: BGLoggerType {
// static let shared = BGLogger()
// private init() {}
private let log: SwiftyBeaver.Type = {
let log = SwiftyBeaver.self
// add log destinations. at least one is needed!
let console = ConsoleDestination() // log to Xcode Console
log.addDestination(console)
return log
}()
func verbose(_ message: String, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
log.verbose(message, file, function, line: line)
}
func debug(_ message: String, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
log.debug(message, file, function, line: line)
}
func info(_ message: String, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
log.info(message, file, function, line: line)
}
func warning(_ message: String, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
log.warning(message, file, function, line: line)
}
func error(_ message: String, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
log.error(message, file, function, line: line)
}
}
Build iOS App from scratch - Part 4 - Dependency container || Swinject || Singleton
import Foundation
import Swinject
final class Injection {
static let shared = Injection()
var container: Container {
get {
if _container == nil {
_container = buildContainer()
}
return _container!
}
set {
_container = newValue
}
}
private var _container: Container?
private func buildContainer() -> Container {
let container = Container()
container.register(BGLoggerType.self) { _ in
return BGLogger()
}
return container
}
}
@propertyWrapper struct Injected<Dependency> {
let wrappedValue: Dependency
init() {
self.wrappedValue = Injection.shared.container.resolve(Dependency.self)!
}
}
import Foundation
class ContentViewModel: ObservableObject {
@Injected private var logger: BGLoggerType
// init(logger: BGLoggerType = BGLogger()) {
// self.logger = logger
// }
func onAppear() {
logger.info("View is loaded")
}
}
Build iOS App from scratch - Part 5 - Localization in SwiftUI
Build iOS App from scratch - Part 6- Localization - Best approach using SwiftGen
if which swiftgen >/dev/null; then
swiftgen
else
echo "warning: SwiftGen not installed, download it from https://github.com/SwiftGen/SwiftGen"
fi
Build iOS App from scratch - Part 7- SwiftFormat - Clean Code in Swift https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md
if which swiftformat >/dev/null; then
swiftformat .
else
echo "warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat"
fi
Build iOS App from scratch - Part 8- Setup DEV, QA and PROD environment using schemes in Xcode
ConfigurationManager.swift
private enum BuildConfiguration {
enum Error: Swift.Error {
case missingKey, invalidValue
}
static func value<T>(for key: String) throws -> T where T: LosslessStringConvertible {
guard let object = Bundle.main.object(forInfoDictionaryKey: key) else {
throw Error.missingKey
}
switch object {
case let string as String:
guard let value = T(string) else { fallthrough }
return value
default:
throw Error.invalidValue
}
}
}
print(API.baseURL)
enum ConfigurationManager {
enum Enviroment {
case dev
case qa
case prod
}
static var enviroment: Environment {
#if DEV
return .dev
#elseif QA
return .qa
#elseif PROD
return .prod
#endif
}
}
# Name of the resource we're selectively copying
GOOGLESERVICE_INFO_PLIST=GoogleService-Info.plist
# Get references to dev and prod versions of the GoogleService-Info.plist
# NOTE: These should only live on the file system and should NOT be part of the target (since we'll be adding them to the target manually)
GOOGLESERVICE_INFO_DEV=${PROJECT_DIR}/${TARGET_NAME}/Firebase/Dev/${GOOGLESERVICE_INFO_PLIST}
GOOGLESERVICE_INFO_PROD=${PROJECT_DIR}/${TARGET_NAME}/Firebase/Prod/${GOOGLESERVICE_INFO_PLIST}
GOOGLESERVICE_INFO_QA=${PROJECT_DIR}/${TARGET_NAME}/Firebase/Qa/${GOOGLESERVICE_INFO_PLIST}
# Make sure the dev version of GoogleService-Info.plist exists
echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_DEV}"
if [ ! -f $GOOGLESERVICE_INFO_DEV ]
then
echo "No Development GoogleService-Info.plist found. Please ensure it's in the proper directory."
exit 1
fi
# Make sure the prod version of GoogleService-Info.plist exists
echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_PROD}"
if [ ! -f $GOOGLESERVICE_INFO_PROD ]
then
echo "No Production GoogleService-Info.plist found. Please ensure it's in the proper directory."
exit 1
fi
# Make sure the prod version of GoogleService-Info.plist exists
echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_PROD}"
if [ ! -f $GOOGLESERVICE_INFO_QA ]
then
echo "No QA GoogleService-Info.plist found. Please ensure it's in the proper directory."
exit 1
fi
# Get a reference to the destination location for the GoogleService-Info.plist
PLIST_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
echo "Will copy ${GOOGLESERVICE_INFO_PLIST} to final destination: ${PLIST_DESTINATION}"
# Copy over the prod GoogleService-Info.plist for Release builds
if [ "${CONFIGURATION}" == "Debug-PROD" ]
then
echo "Using ${GOOGLESERVICE_INFO_PROD}"
cp "${GOOGLESERVICE_INFO_PROD}" "${PLIST_DESTINATION}"
elif [ "${CONFIGURATION}" == "Debug-QA" ]
then
echo "Using ${GOOGLESERVICE_INFO_QA}"
cp "${GOOGLESERVICE_INFO_QA}" "${PLIST_DESTINATION}"
else
echo "Using ${GOOGLESERVICE_INFO_DEV}"
cp "${GOOGLESERVICE_INFO_DEV}" "${PLIST_DESTINATION}"
fi
✅Part 1: Using a singleton
class ContentViewModel: ObservableObject {
@Injected private var logger: BGLoggerType
func onAppear() {
FirebaseAnalyticsManager.shared.logEvent(name: "onboarding_test_1", param: [:])
}
}
import Firebase
import Foundation
class FirebaseAnalyticsManager {
static let shared = FirebaseAnalyticsManager()
func logEvent(name: String, param: [String: Any]) {
Analytics.logEvent(name, parameters: param)
}
}
✅Decouple
AnalyticsManager.swift
import Foundation
protocol EventProtocol {
var name: String { get }
var param: [String: Any] { get }
}
// struct Event: EventProtocol {
// var name: String
// var param: [String: Any] = [:]
// }
enum AnalyticsEvent {
case clickButtonEvent
case chapteredTapped(name: String)
}
extension AnalyticsEvent: EventProtocol {
var name: String {
switch self {
case .clickButtonEvent:
return "click_button_event"
case .chapteredTapped:
return "chapter_tapped"
}
}
var param: [String: Any] {
switch self {
case .clickButtonEvent:
return [:]
case let .chapteredTapped(name):
return ["name": name]
}
}
}
protocol AnalyticsEventLoggerProtocol {
func initialize()
func logEvent(event: EventProtocol)
}
protocol AnalyticsManagerProtocol {
func logEvent(event: EventProtocol)
}
final class AnalyticsManager: AnalyticsManagerProtocol {
private let logger: AnalyticsEventLoggerProtocol
init(logger: AnalyticsEventLoggerProtocol) {
self.logger = logger
}
func logEvent(event: EventProtocol) {
logger.logEvent(event: event)
}
}
FirebaseAnalyticsManager.swift
import Firebase
import Foundation
final class FirebaseAnalyticsManager: AnalyticsEventLoggerProtocol {
func initialize() {}
func logEvent(event: EventProtocol) {
Analytics.logEvent(event.name, parameters: event.param)
}
}
final class Injection {
static let shared = Injection()
var container: Container {
get {
if _container == nil {
_container = buildContainer()
}
return _container!
}
set {
_container = newValue
}
}
private var _container: Container?
private func buildContainer() -> Container {
let container = Container()
container.register(BGLoggerType.self) { _ in
BGLogger()
}
container.register(AnalyticsManagerProtocol.self) { _ in
AnalyticsManager(logger: FirebaseAnalyticsManager())
}
return container
}
class ContentViewModel: ObservableObject {
@Injected private var logger: BGLoggerType
@Injected private var analyticsManager: AnalyticsManagerProtocol
// init(logger: BGLoggerType = BGLogger()) {
// self.logger = logger
// }
func onAppear() {
logger.info("View is loaded")
print(API.baseURL)
// analyticsManager.logEvent(event: Event(name: "onbording_test_2"))
analyticsManager.logEvent(event: AnalyticsEvent.clickButtonEvent)
}
}