무료 폰트를 찾아서..

앱을 만들려다 보니 저작권들이 거슬린다.
경제적 자유! 그것을 위한 우리의 여행은 끝이 나지 않는다..

아래 링크들은 유투버를 통해서 알게 된 링크이다.
라이센스를 잘 읽어보고, 글꼴을 사용하길 바란다.
경우에 따라 유료인것도 있어서…

1.검은고딕 https://github.com/zesstype/Black-Han…

2.에스코어드림 http://www.s-core.co.kr/who-we-are/font/

3.몬소리체 https://brunch.co.kr/@creative/32

4.Noto Sans https://fonts.google.com/specimen/Not…

5.잉크립퀴드체 http://www.thefaceshop.com/event/lipq…

6.tvn 즐거운 이야기체 http://tvn10festival.tving.com/playgr…

7.스웨거체 http://www.swagger.kr/font.html

8.주아체 http://font.woowahan.com/

눈누 https://noonnu.cc/

구글폰트 https://fonts.google.com/

  • 참고 유튜버 영상 링크
  • https://www.youtube.com/watch?v=7gOiGK83X-o

@escaping

다음과 같은 블럭함수를 사용할 경우에, 에러가 발생한다.
“Escaping closure captures non-escaping parameter ‘completion'”

    func testFunc(_ completion: (String?) -> Void ) {
        DispatchQueue.global().async {
            print("global something...")
            
            DispatchQueue.main.async {
                completion("End")
            }
        }
    }

원인은 testFunc이 종료되고 나서 completion이 호출되는데, 호출이 불가하기 때문이다.
이러한 경우에 @escaping 을 선언해주면 정상 동작을 한다.

    func testFunc(_ completion: @escaping (String?) -> Void ) {
        DispatchQueue.global().async {
            print("global something...")
            
            DispatchQueue.main.async {
                completion("End")
            }
        }
    }

여기까지는 모두 알고 있는 항목이였을 것이다.
굳이 뭐 이런 글을이라며….
하지만, 오늘 배운게 하나 있는데, completion이 옵셔널로 선언된다면, default값으로 @escaping이 설정된다는 것이였다.


다음과 같이 코드 작성이 가능하다는 말이다.

    func testFunc(_ completion: ((String?) -> Void)? ) {
        DispatchQueue.global().async {
            print("global something...")
            
            DispatchQueue.main.async {
                completion?("End")
            }
        }
    }

난 분명 까먹을것이기 때문에, 기록을 남긴다.

protocol Equatable

글 작성중입니다.
글 하나 작성하는데 상당히 오랜 시간이 필요하네요..
곧 마무리 해서 올리겠습니다.

iOS 개발을 하다보면 꽤 많은 프로토콜들이 등장한다.
대부분의 개발자들은 별 생각없이 넘어갈것이라고 생각한다.
그러다가 어느순간에 이건 도대체 뭐지? 라는 의문이 들것이고 이에 대해 알고 싶어질것이라 생각한다.
지금 내가 그렇고, 매번 궁금한게 생길때마다 다시 찾아보는 수고를 덜기 위해 정리해보려고 한다.

equatable| ikwéitəbl | 형용사 | 동일시할 수 있는.

아래 애플링크에 있는 글들을 정리를 좀 해볼께요.
좀 보기 편하게 제 스타일대로 의역을 좀 해놨습니다.
틀린부분이 있다면 알려주세요.

Equatable

Type의 같음을 비교할 수 있다.

개요

Equatable 프로토콜을 따르는 Type들은 equal-to operator (==) 또는 불균형인지를 not-equal-to operator (!=)를 사용하여 비교 할수 있다.

배열와 컬렉션은 Equatable을 만족할때, 쉽게 비교를 수행할수 있다.
예를 들면 다음과 같다.
배열의 요소들이 Equatable을 만족할 때, 제공되는 closure를 사용하여 비교하지 않고, 특별한 값을 포함하고 있는지를 contains(_:) 메소드를 사용하여 체크할수 있다.

let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"]

let nameToCheck = "Kofi"
if students.contains(nameToCheck) {
    print("\(nameToCheck) is signed up!")
} else {
    print("No record of \(nameToCheck).")
}
// Prints "Kofi is signed up!"

Equatable Protocol을 따르기

Equatable을 당신이 만든 Type에 추가한다는 것은, Collection에서 특별한 인스턴스를 찾기 위해 convenient APIs를 사용한다는 뜻이다.
Equatable은 base protocol이며, Hashable과 Comparable protocols에도 사용된다.
그리고, Collection의 요소들을 sets과 sorting하여 구성하는 것처럼, custom type을 사용할수 있게 해준다.

Equtable을 만족시며 type를 생성하기 위해서는 다음 두가지를 충족시켜야 한다.

  • Struct일 경우, 모든 properties가 Equatable을 만족 시켜야 한다.
  • Enum일 경우, 모든 value들이 Equatable을 만족 시켜야 한다.
    (value없는 enum은 선언없이 만족된다. 맞는 해석인지 모르겠다. 테스트 필요.)

작성중…

  • 참고 링크
  • https://developer.apple.com/documentation/swift/equatable

Pod install시 에러 발생 [ParserError – 767]

ParserError – 767: unexpected token at ‘ ‘ #10088

Pod 설치시 에러가 나면 곤란한 경우가 많다.
처음보는 에러가 하나 발생했다.
pod repos와 캐시를 클린해주면 해결이 된다.

해결방법은 다음과 같다.

rm -rf ~/.cocoapods/repos
pod cache clean --all
pod install
  • 참고 링크
  • https://github.com/CocoaPods/CocoaPods/issues/10088

[글 작성 중] Firebase 클라우드 메시지 시작하기

Firebase에서 제공하는 푸쉬를 적용해보자.
Pod을 사용할 것이며, 해당 가이드 링크는 참고링크(하단)에 적어놓았다.
https://firebase.google.com/docs/cloud-messaging/ios/client?authuser=0

Firebase에서 프로젝트를 설정하고, 푸쉬를 사용하기 위한 방법입니다.
프로젝트 설정등은 firebase 가이드를 따라 하는 방식을 권장합니다.
따라서 firebase를 XCode에 적용하고, 푸쉬가 오는것까지 확인하는 것을 목표로 작성된 문서입니다.

다음과 같이 Pod을 설정하고 설치한다.
Pod 설정은 가이드 문서를 참고하여 각자의 환경에 맞추어 설정한다.

# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'


target 'SampleRxSwift' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!


  # Pods for SampleRxSwift
  #pod 'RxSwift', '6.0.0-rc.1'
  #pod 'RxCocoa', '6.0.0-rc.1'
  # Add the Firebase pod for Google Analytics
  pod 'Firebase/Analytics'
  # Add the pod for Firebase Cloud Messaging
  pod 'Firebase/Messaging'




  target 'SampleRxSwiftTests' do
    inherit! :search_paths
    # Pods for testing


    # Add the Firebase pod for Google Analytics
    pod 'Firebase/Analytics'
    # Add the pod for Firebase Cloud Messaging
    pod 'Firebase/Messaging'


  end


  target 'SampleRxSwiftUITests' do
    # Pods for testing
    # Add the Firebase pod for Google Analytics
    pod 'Firebase/Analytics'
    # Add the pod for Firebase Cloud Messaging
    pod 'Firebase/Messaging'


  end


end

Pod 설치 및 사용법은 아래 링크를 참고. 또는 가이드 문서를 참고
http://magpiebros.com/wordpress/category/dev-story/ios-개발-이야기/cocoapods/

앱 실행부, AppDelegate 정당한 위치에 아래와 같은 같은 코드를 삽입한다.

class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        print("[AppDelegate] didFinishLaunchingWithOptions")
        FirebaseApp.configure()
        firebaseRegisterForRemoteNotifications(application)


        // 메시지 대리자 설정
        Messaging.messaging().delegate = self
        
        return true
    }

FCM관련 추가

// MARK:- FCM
extension AppDelegate {
    func getFCMToken() {
        print("[AppDelegate] getFCMToken")

        Messaging.messaging().token { token, error in
            if let error = error {
                print("Error fetching FCM registration token: \(error)")
            } else if let token = token {
                print("FCM registration token: \(token)")
                //            self.fcmRegTokenMessage.text  = "Remote FCM registration token: \(token)"
            }
        }
    }
    
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
        print("[AppDelegate] 파이어베이스 토큰: \(fcmToken)")
    }
    
    func firebaseRegisterForRemoteNotifications(_ application: UIApplication) {
        print("[AppDelegate] firebaseRegisterForRemoteNotifications")
        if #available(iOS 10.0, *) {
            // For iOS 10 display notification (sent via APNS)
            UNUserNotificationCenter.current().delegate = self
            
            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(
                options: authOptions,
                completionHandler: {_, _ in })
        } else {
            let settings: UIUserNotificationSettings =
                UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)
        }
        
        application.registerForRemoteNotifications()
    }
}

APNS 설정

// MARK:- APNS
extension AppDelegate {
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print("[AppDelegate] 토큰 받음", deviceToken.description)
        Messaging.messaging().apnsToken = deviceToken
    }
    
    
    // 호출되지 않음. iOS 10 미만?
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
        print("[AppDelegate] didReceiveRemoteNotification - 알람 옴")
        // If you are receiving a notification message while your app is in the background,
        // this callback will not be fired till the user taps on the notification launching the application.
        // TODO: Handle data of notification
        
        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)
        
        // Print message ID.
        //      if let messageID = userInfo[gcmMessageIDKey] {
        //        print("Message ID: \(messageID)")
        //      }
        
        // Print full message.
        print(userInfo)
    }
    
    // 호출되지 않음. iOS 10 미만?
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        print("[AppDelegate] didReceiveRemoteNotification - fetchCompletionHandler - 알람 옴")
        // If you are receiving a notification message while your app is in the background,
        // this callback will not be fired till the user taps on the notification launching the application.
        // TODO: Handle data of notification
        
        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)
        
        // Print message ID.
        //      if let messageID = userInfo[gcmMessageIDKey] {
        //        print("Message ID: \(messageID)")
        //      }
        
        // Print full message.
        print(userInfo)
        
        completionHandler(UIBackgroundFetchResult.newData)
    }
}


//
@available(iOS 10, *) 
extension AppDelegate : UNUserNotificationCenterDelegate {
    
    // Receive displayed notifications for iOS 10 devices.
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        print("[AppDelegate] UNUserNotificationCenterDelegate")

        let userInfo = notification.request.content.userInfo
        
        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)
        
        // Print message ID.
        //    if let messageID = userInfo[gcmMessageIDKey] {
        //      print("Message ID: \(messageID)")
        //    }
        
        // Print full message.
        print(userInfo)
        
        // Change this to your preferred presentation option
        completionHandler([[.alert, .sound]])
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        print("[AppDelegate] UNUserNotificationCenterDelegate")

        let userInfo = response.notification.request.content.userInfo
        // Print message ID.
        //    if let messageID = userInfo[gcmMessageIDKey] {
        //      print("Message ID: \(messageID)")
        //    }
        
        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)
        
        // Print full message.
        print(userInfo)
        
        completionHandler()
    }
}

이렇게 코드만 적어놓는것은 역시 도움이 되지 않는 글을 작성해 놓은것 같아 씁쓸한 마음이 남는다. 이것보다 좀더 도움도 되고 발전도 할 수 있는 글을 작성할 수 있도록 고민해보아야 겠다.

  • 참고 링크
  • https://firebase.google.com/docs/cloud-messaging/ios/client?authuser=0

[Xcode] wifi로 iPhone에 앱 설치하기

아이폰에 usb선을 꼽아서 빌드를 하곤한다.
하지만 wifi로 빌드를 해서 넣을수 있다면?
오늘은 이 방법에 대해 글을 남기려고 한다.

wifi망을 쓸수 없는 개발환경에서만 개발을 해서 별로 필요가 없었는데, 집에서 개발을 하려고 하다보니 몹시 불편했다.
wifi로 앱을 빌드해서 설치하는 방법을 공유한다.

Window->Devices and Simulators를 선택하면 다음과 같은 화면이 나온다.

Connect via network 체크박스를 선택해주면 완료

단, 설정시 최초 한번은 usb 연결이 필요하다.

[CocoaPods] 01. 기본 사용법

요즘(요즘이라기 보단.. 오래되었지만.. 쿨럭) 뭔가를 대충 쓰기 보다는 제대로 알고 써보고 싶다는 생각이 많아진다.
그래서 공부도 하고 레퍼런스도 남길겸, 글들을 정리해서 써보기로 마음먹었다.

“CocoaPods은 Swift와 Objective-C 코코아 프로젝트를 관리하는데 의존적으로 사용된다. 7만8천개 이상의 라이브러리가 존재하며, 이를 통해 만들어진 앱은 3백만개 이상이다. 코코아팟(CocoaPods)는 당신의 프로젝트를 우아하게 만드는데 도움이 될것이다.”라고 코코아팟 사이트에서는 알려주고 있다.

뭘 먼저 해봐야 할가? 설치 아니겠는가?
설치를 해보자.

sudo gem install cocoapods

뭐 명령어 한줄만 치면 자연스럽게 설치가 이루어진다.

이제 iOS 프로젝트 위치로 이동을 해주고 pod을 install해준다.
(‘당신의 프로젝트명’.xcodeproj 파일이 존재하는 위치여야 한다.)

cd /Users/magpiebros/Documents/dev/iOS/SampleRxSwift/SampleRxSwift
pod init

pod을 init하고 나면, Podfile이 생성된다.
아래와 같은 모양의 파일이 생성되었을 것이다.

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'SampleRxSwift' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for SampleRxSwift

  target 'SampleRxSwiftTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'SampleRxSwiftUITests' do
    # Pods for testing
  end

end

이제 # Pods for SampleRxSwift 등의 위치에 원하는 Pod을 설정해주고 install만 해주면 모든 준비가 끝이난다.

난 RxSwift를 설치할것이기 때문에 https://cocoapods.org/pods/RxSwift 에서 가이드 문서를 한번 읽어보고 진행할것이다.
다음과 같이 설정하면, 샘플앱에서 동작하는것이 모두 동작한다고 하니, 다음과 같이 설정해놓고 진행해보겠다.
(각 라이브러리마다 사용법은 다를수 있으니, 가이드 문서를 참고해야 한다.)

# Podfile
use_frameworks!

target 'YOUR_TARGET_NAME' do
    pod 'RxSwift', '6.0.0-rc.1'
    pod 'RxCocoa', '6.0.0-rc.1'
end

# RxTest and RxBlocking make the most sense in the context of unit/integration tests
target 'YOUR_TESTING_TARGET' do
    pod 'RxBlocking', '6.0.0-rc.1'
    pod 'RxTest', '6.0.0-rc.1'
end

이제 내 프로젝트에 있는 Podfile을 변경해보자.

# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'

target 'SampleRxSwift' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for SampleRxSwift
  pod 'RxSwift', '6.0.0-rc.1'
  pod 'RxCocoa', '6.0.0-rc.1'

  target 'SampleRxSwiftTests' do
    inherit! :search_paths
    # Pods for testing
    pod 'RxBlocking', '6.0.0-rc.1'
    pod 'RxTest', '6.0.0-rc.1'
  end

  target 'SampleRxSwiftUITests' do
    # Pods for testing
    pod 'RxBlocking', '6.0.0-rc.1'
    pod 'RxTest', '6.0.0-rc.1'
  end

end

그리고 pod install을 하면 모든 준비는 끝이난다.
몇가지 경고들이 나타나는데, ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES 관련 에러등이 생긴다.

하지만 사용에는 무리가 없으니, 차차 더 깊은 Pod사용법을 정리해보겠다.
아.. 아직 RxSwift는 뭘 써야 좋을지를 정하지 못한 상태다.
한번 길을 잘못가면 되돌아 오기도 쉽지 않으니, 자세히 알아보고 시작하겠다.

  • 참고 사이트
  • https://cocoapods.org
  • https://cocoapods.org/pods/RxSwift

억음부호 `Swift`

나이가 들어가면서, 점점 미래가 불안해지고 뭐하고 먹고 살아가야하나 고민을 하고 있다.

그래서, 내가 제일 잘 할수 있는 코딩(?)을 더 잘하려고 한다.
늦은감이 있지만, RxSwift부터 최신트랜드를 학습하며 따라가려고 노력중이다.
Swift를 좀 깊게 학습하고 싶어서 코쟁이 형들하고, 잘한다는 형들의 소스를 보고 있는 중이다.

그러던 오늘, 억음이라고 불리는 ` 표시의 소스코드를 발견하였다.
이 표시가 억음이라는걸 알아내는데도 꽤 시간이 걸렸다. 영어로는 backtick이라고 표현하는것 같다.(추측..)

Martin이라는 멋진형이 쓴글을 직독해서 블로그한다.
마틴형 링크는 하단에 표기해놓았다. 똑똑한 형 같으니 그 블로그를 뒤적거려보는것도 좋을것 같다.
오역이 있을수 있으니, 오역이나 더 좋은 표현이 있다면 댓글로 남겨주길 부탁한다. (반말로 쓰는 이유는 내 블로그에 내가 쓰면서 존칭을 쓰면 이상한 기분이 들어서랄까…)

`은 language keywords로부터 identifier를 만들수 있게 해준다고 한다.

Swift에서 C 또는 Objective-C function을 직접 호출할수 있다, 그러나 C function이름이 guard라면 억음 없이 호출할수 없다.

`guard`()

더 좋은 예제로 enum을 들수 있다. 다음과 같은 값들을 가진 enum을 만들고 싶을수 있다.

enum Foo {
    case `var`
    case `let`
    case `class`
    case `func`
}

let items = [Foo.class, Foo.let, .var]

억음을 사용하면 reserved language keywords를 사용할 수 있다.

당신이 애플 API 디자인처럼 default keyword를 property 이름으로 사용하고 싶다면, 이때 억음을 사용할 수 있다.
NotificationCenter가 싱글톤으로 default를 사용하고 있으며, 이 default는 keyword이며 switch statements로 사용된다.
이를 구현하기 위해서는 다음과 같이 사용하면 된다.

class MyOwnNotificationCenter {
    static let `default` = MyOwnNotificationCenter()
    ...
}

클로져에서 [weak self]를 사용해본적이 있나요? 그렇지 않다면 사용하셔야 합니다.
self를 weak reference하게 만들었을때, self는 optional이고 가끔씩은 문제를 일으키게 됩니다.
Swift4.2 이전에 strong reference self의 이름을 유지하기 위해 다음과 같이 사용했습니다. (또는 strongSelf로 이름을 바꾸어서 사용)

self.provider.fetchData { [weak self] (fetchedData) in
    guard let `self` = self else { return }
    let processedData = self.processData(fetchedData)
    self.updateUI(with: processedData)
}

Swift4.2에서는 다음과 같이 사용합니다.

self.provider.fetchData { [weak self] (fetchedData) in
    guard let self = self else { return }
    let processedData = self.processData(fetchedData)
    self.updateUI(with: processedData)
}

이제 optional binding을 사용해서 weak를 strong으로 reference를 업그레이드 하였습니다.

  • 참고 사이트
  • https://blog.entirely.digital/swift-backticks/

ios 아카이브에 불필요 앱 삭제

XCode에서 아카이브를 생성할 경우에 보기 싫은 앱들이 나타날 경우가 있다. 개인 작업하는 것들이 회사에 나타난다거나.. 등등

이럴때 아래와 같이 해당 경로로 이동해서 해당 앱 번들 아이디를 지워주고 XCode를 재실행 해주면 더이상 나타나지 않는다.
XCode설정에서 자동으로 동기화 해주는 기능이 꺼져있어야만, 자동으로 다시 생성하지 않을 것이다

ㅊ~/Library/Developer/Xcode/Products/해당 앱 번들 아이디 제거

apple 키체인 프로비져닝 오류가 날때

앱 프로비저닝 오류로 빌드가 안될 경우에는 여러가지 케이스가 존재하지만, 확실한 방법중 하나는 아래 경로로 이동해서 모든 프로비저닝을 삭제후 다시 설치해서 사용하는 것이다.

하나 하나 열어서 비교해볼수도 있지만, 다 지우는게 속 편할 것이다

~/Library/MobileDevice/Provisioning Profiles