GitaFinal

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

スクリーンショット 2023-03-10 17 27 07

スクリーンショット_2023_03_10_17_29

スクリーンショット_2023_03_10_17_31

スクリーンショット 2023-03-10 17 25 15

スクリーンショット 2023-03-10 17 21 25

スクリーンショット 2023-03-10 17 19 11

スクリーンショット_2023_03_10_17_51

NowPlaying_と_Item-0_と_Item-0_と_Sound

スクリーンショット_2023_03_10_18_15

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)

スクリーンショット 2023-03-10 19 32 16

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
    }
}

スクリーンショット_2023_03_19_20_19

# 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)
    }
}