20210329のiOSに関する記事は11件です。

【Swift】SlideshowAppで学んだこと

はじめに

SlideshowAppを作成しましたので、そこで学んだことの備忘録です。

GitHub

学んだこと

グラデーションの扱いです。

let width = self.view.frame.size.width
let height = self.view.frame.size.height
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
gradientLayer.colors = [UIColor(red: 0.8, green: 0.0, blue: 0.73, alpha: 1).cgColor,
                        UIColor(red: 0.1, green: 0.0, blue: 0.40, alpha: 1).cgColor]
gradientLayer.startPoint = CGPoint.init(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint.init(x: 1, y: 0.5)
self.view.layer.insertSublayer(gradientLayer, at: 0)

このようにすることで、背景色がグラデーションでいい感じになってくれます。
公式ドキュメント: CAGradientLayer
スニペットなどに登録しておくことをお勧めします!

おわりに 

おわりです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Extensionまとめ(初級)

はじめに

Extensionの基礎的な扱い方をまとめました。

Extensionとは?

Extensionキーワードを使うことで既存の型に方を構成する要素(プロパティやメソッドやイニシャライザ)を追加する事ができます。
具体例を見ていきましょう。

メソッドでの利用

以下のようにすることで、plusOneメソッドをIntに追加できます。
selfはこのメソッドの呼び出し元(今回は5)を表しています。(詳細は後述します)

extension Int {
    func plusOne() -> Int {
        return self + 1
    }
}
print(5.plusOne()) // 6

プロパティでの利用

extensionはストアドプロパティは追加できませんが、コンピューテッドプロパティを追加する事ができます。

extension Int {
    var minus10: Int {
        return self - 10
    }
}
print(100.minus10) // 90

イニシャライザでの利用

イニシャライザを追加してみます。

enum BloodType {
    case A
    case B
    case O
    case AB
    var title: String {
        return "あなたの血液型は"
    }
    var message: String {
        switch self {
        case .A: return "Aです"
        case .B: return "Bです"
        case .O: return "Oです"
        case .AB: return "ABです"
        }
    }
}

extension UIAlertController {
    convenience init(blood: BloodType) {
        self.init(title: blood.title,
                  message: blood.message,
                  preferredStyle: .alert)
    }
}

let blood = BloodType.AB
let alert = UIAlertController(blood: blood)

プロトコルでの利用

プロトコルをエクステンションすることもできます。

protocol Book {
    var name: String { get }
    var price: Int { get }
}

extension Book {
    func printName() {
        print(name)
    }
    func printPrice() {
        print(price)
    }
    func printSelf() {
        print(self)
    }
    func printMyType() {
        print(type(of: self))
    }
}

struct MyBook: Book {
    var name: String
    var price: Int
}

let myBook = MyBook(name: "マイブック", price: 1000)
myBook.printPrice() // 1000
myBook.printSelf() // MyBook(name: "マイブック", price: 1000)
myBook.printMyType() // MyBook

これでBookプロトコルを採用した型はextension Book内のメソッドを扱う事ができます。

制約の追加

プロトコルエクステンションは型による制約をつけることができ、この条件を満たすもののみプロトコルエクステンションを有効にできます。

extension Collection where Element == Int {
    var total: Int {
        return self.reduce(0) { $0 + $1 }
    }
}

let intArray = [1, 2, 3, 4, 5]
print(intArray.total) // 15
let stringArray = ["a", "b", "c"]
print(stringArray.total) // error

条件はCollectionの要素ElementIntであると言う意味です。ですので、intArrayの要素はIntなのでtotalプロパティを扱う事ができますが、stringArrayは要素がStringなのでtotalプロパティにアクセスしようとするとエラーになります。

おわりに

エクステンションの基礎的な使い方を紹介しました。以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Extensionまとめ(基礎)

はじめに

Extensionの基礎的な扱い方をまとめました。

Extensionとは?

Extensionキーワードを使うことで既存の型に方を構成する要素(プロパティやメソッドやイニシャライザ)を追加する事ができます。
具体例を見ていきましょう。

メソッドでの利用

以下のようにすることで、plusOneメソッドをIntに追加できます。
selfはこのメソッドの呼び出し元(今回は5)を表しています。(詳細は後述します)

extension Int {
    func plusOne() -> Int {
        return self + 1
    }
}
print(5.plusOne()) // 6

プロパティでの利用

extensionはストアドプロパティは追加できませんが、コンピューテッドプロパティを追加する事ができます。

extension Int {
    var minus10: Int {
        return self - 10
    }
}
print(100.minus10) // 90

イニシャライザでの利用

イニシャライザを追加してみます。

enum BloodType {
    case A
    case B
    case O
    case AB
    var title: String {
        return "あなたの血液型は"
    }
    var message: String {
        switch self {
        case .A: return "Aです"
        case .B: return "Bです"
        case .O: return "Oです"
        case .AB: return "ABです"
        }
    }
}

extension UIAlertController {
    convenience init(blood: BloodType) {
        self.init(title: blood.title,
                  message: blood.message,
                  preferredStyle: .alert)
    }
}

let blood = BloodType.AB
let alert = UIAlertController(blood: blood)

プロトコルでの利用

プロトコルをエクステンションすることもできます。

protocol Book {
    var name: String { get }
    var price: Int { get }
}

extension Book {
    func printName() {
        print(name)
    }
    func printPrice() {
        print(price)
    }
    func printSelf() {
        print(self)
    }
    func printMyType() {
        print(type(of: self))
    }
}

struct MyBook: Book {
    var name: String
    var price: Int
}

let myBook = MyBook(name: "マイブック", price: 1000)
myBook.printPrice() // 1000
myBook.printSelf() // MyBook(name: "マイブック", price: 1000)
myBook.printMyType() // MyBook

これでBookプロトコルを採用した型はextension Book内のメソッドを扱う事ができます。

制約の追加

プロトコルエクステンションは型による制約をつけることができ、この条件を満たすもののみプロトコルエクステンションを有効にできます。

extension Collection where Element == Int {
    var total: Int {
        return self.reduce(0) { $0 + $1 }
    }
}

let intArray = [1, 2, 3, 4, 5]
print(intArray.total) // 15
let stringArray = ["a", "b", "c"]
print(stringArray.total) // error

条件はCollectionの要素ElementIntであると言う意味です。ですので、intArrayの要素はIntなのでtotalプロパティを扱う事ができますが、stringArrayは要素がStringなのでtotalプロパティにアクセスしようとするとエラーになります。

おわりに

エクステンションの基礎的な使い方を紹介しました。以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】Gmail APIでメール情報を取得

目的と手順の説明

今回は、私の最新のGmailを誰から受信したのか?を取得することを目的にするよ〜〜〜〜〜。

1.Gmail APIを有効にする

2.認証画面を作成

3.OAuth2.0を利用して、クライアントアプリにトークンを受けとる

4.Gmail APIを叩く

下の記事を読んでから、この記事を読み進めることをおすすめします。
一番分かりやすい OAuth の説明

1.Gmail APIを有効にする

(もしかしたら、1より先に2をしなあかんかったかもしれん。。。)
Gmail APIを有効
アプリを公開しないなら、全てを記述する必要がないので5分ぐらいで終わります。

認証情報の隣にある認証情報を作成というボタンをクリックしてOuthクライアントIDを選択する
→アプリケーションの種類をiOSにする→バンドルIDはXcodeで作成したアプリのBoundleIdentifierをコピペする。

2.認証画面を作成

認証画面は、クライアントアプリがアクセストークンを作成する時に、エンドユーザーに対して認可する権限を与える画面です。この画面自体プログラマーが作る必要がありません。もうすでに用意されている。
認証画面の途中でスコープを聞かれると思います。そのとき、制限付きのスコープのhttps://mail.google.com/を選択する。(今回はメール情報を取得するから)
テストユーザーも追加しておく

これでサーバーサイドの設定はしゅーーーりょーーーーーーーーう!!
これから、Xcodeで作業します。

3.OAuth2.0を利用して、クライアントアプリにトークンを受けとる

OAuthを利用する準備

この記事みながらセットアップした。
ライブラリのインストールにCocoaPodsを使用します。
ターミナルを開けて〜〜〜〜〜

cd アプリのディレクトリ

アプリのディレクトリに移動します。

 pod init

ポッドファイルを作成。

    pod 'GoogleAPIClientForREST/Gmail', '~> 1.3.0'
    pod 'GTMAppAuth'

podファイルに上記を追加して

pod update

アップデートしたら、大丈夫だぁ〜〜〜〜〜

アクセストークンをリクエスト

今回使う変数を全て定義するぞ〜
その前に、したのインポートを忘れずに

import AppAuth
import GoogleAPIClientForREST
import GTMAppAuth
private let scopes = ["https://mail.google.com/"]
private let kClientID = "クライアントID"
private let kRedirectURL  = URL.init(string: "リダイレクトURL"+":/oauthredirect")!
private let configuration = GTMAppAuthFetcherAuthorization.configurationForGoogle()
private var authorization: GTMAppAuthFetcherAuthorization?
private let userId=Gメールアドレス

scopes

2の認証画面の設定で使用したスコープをいれる。

kClientIDとkRedirectURL

google.com/api
上のリンクから認証情報のOAuth2.0クライアントIDに追加されているアプリの編集画面に行けばみれる!
クライアントIDiOSのURLスキームあります。
kClientID=クライアントID
kRedirectURL=iOSのURLスキーム+":/oauthredirect"です
(:/oauthredirect"を忘れずに! 俺は忘れていた。)

認証画面を表示させる

private func showAuthorizationDialog() {
        let request = OIDAuthorizationRequest.init(configuration: configuration,
                                                   clientId: self.kClientID,
                                                   scopes: scopes,
                                                   redirectURL: kRedirectURL,
                                                   responseType: OIDResponseTypeCode,
                                                   additionalParameters: nil)

        let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.currentAuthorizationFlow=OIDAuthState.authState(byPresenting: request, presenting: self, callback: { (authState, err) in
            if let error = err {
                NSLog("\(error)")
            } else {
                if let authState = authState {
                    self.authorization = GTMAppAuthFetcherAuthorization.init(authState: authState)
                    GTMAppAuthFetcherAuthorization.save(self.authorization!, toKeychainForName: "authorization")
                }
            }
        })
    }

軽く説明していきます。

let request = OIDAuthorizationRequest.init(configuration: configuration,
                                                   clientId: self.kClientID,
                                                   scopes: scopes,
                                                   redirectURL: kRedirectURL,
                                                   responseType: OIDResponseTypeCode,
                                                   additionalParameters: nil)

認証画面を表示するためのサーバーに対するリクエストを表しています。

responseType:は応答のタイプを表しています。
additionalParameters:はクライアントの追加の許可パラメーターです。今回はないのでnilを渡します。

let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate

AppDelegateにアクセスしている理由は後ほどわかります。
appDelegateに変数を置いている理由は、異なるSwiftファイル間で変数を共有するためです。
AppDelegateに宣言した変数は、どこのViewControllerでも共有できる。

 appDelegate.currentAuthorizationFlow=OIDAuthState.authState(byPresenting: request, presenting: self, callback: { (authState, err) in
            if let error = err {
                NSLog("\(error)")
            } else {
           //OIDAuthStateからGTMAppAuthFetcherAuthorizationを作成します。
                if let authState = authState {
                    self.authorization = GTMAppAuthFetcherAuthorization.init(authState: authState)
                    GTMAppAuthFetcherAuthorization.save(self.authorization!, toKeychainForName: "authorization")
                }
            }
        })

上記のコードで認証要求を実行します。
appleDelegateクラスにcurrentAuthorizationFlowをOIDExternalUserAgentSession?型で宣言しておく。
より詳しく知りたいかたは下のリンクに飛べ〜〜〜〜〜〜〜〜
README.md

// Serialize to Keychain
GTMAppAuthFetcherAuthorization.save(_authorization, toKeychainForName: kGTMAppAuthExampleAuthorizerKey)

// Deserialize from Keychain
let authorization = GTMAppAuthFetcherAuthorization.init(fromKeychainForName: kGTMAppAuthExampleAuthorizerKey)

// Remove from Keychain
GTMAppAuthFetcherAuthorization.removeFromKeychain(forName: kGTMAppAuthExampleAuthorizerKey)

一番上のコードでキーチェーンを利用して、ログイン状態を保存します。
いくつもログインしたい場合は、名前を変えて保存したらいいだけだから、すごい便利ですね。
したの記事でキーチェーンの理解をしておくといいかももも桃もお
キーチェーンの記事

ここまでで、認証完了!!

4.Gmail APIを叩く

やっと最後の章になります。長かった〜、、、、

コード全体

@objc func message(_ senfder:UIButton){
        let query = GTLRGmailQuery_UsersMessagesList.query(withUserId: userId)
        let service = GTLRGmailService()
        service.authorizer = GTMAppAuthFetcherAuthorization.init(fromKeychainForName: "authorization")
        service.executeQuery( query,
                              delegate: self,
                              didFinish: #selector(callback) )
    }
 @objc func callback( ticket : GTLRServiceTicket,
                             finishedWithObject response : GTLRGmail_ListMessagesResponse,
                             error : NSError? ){
            if let error = error {
                print("メッセージリストの取得に失敗しました。")
                print(error)
                return
            }
        print("メッセージリストの取得に成功しました。")
        guard let message=response.messages?.first else {return}
        guard let messageid=message.identifier else {return}
        guard let url=URL(string: "https://gmail.googleapis.com/gmail/v1/users/\(userId)/messages/\(messageid)") else {return}
        var urlRequest=URLRequest(url: url)
        guard let token=authorization?.authState.lastTokenResponse?.accessToken else {return}
        urlRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        let session=URLSession(configuration: URLSessionConfiguration.default)
        let task=session.dataTask(with: urlRequest){
            (data,urlResponse,err) in
            if let err=err {
                print("失敗しました\(err)")
            }
            do{
                let responseJson=try JSONDecoder().decode(res.self, from: data!)
                responseJson.payload.headers.forEach {
                    if($0.name=="From"){
                        print($0.value)
                    }
                }
            }catch(let err){
                print("失敗した\(err)")
            }
        }.resume()
    }

コードの解説

説明ながなる〜〜〜〜〜〜〜〜疲れた〜〜〜〜〜〜〜〜

@objc func message(_ senfder:UIButton){
        let query = GTLRGmailQuery_UsersMessagesList.query(withUserId: userId)
        let service = GTLRGmailService()
        service.authorizer = GTMAppAuthFetcherAuthorization.init(fromKeychainForName: "authorization")
        service.executeQuery( query,
                              delegate: self,
                              didFinish: #selector(callback) )
    }

messageメソッドはボタンが押されたら呼ばれるメソッドです。
詳しく見ていきます。

let query = GTLRGmailQuery_UsersMessagesList.query(withUserId: userId)

GTLRGmailQuery_UsersMessagesList.queryはユーザーのメールボックス内のメッセージを一覧表示する命令です。useIdはGmailアドレスです。

let service = GTLRGmailService()
service.authorizer = GTMAppAuthFetcherAuthorization.init(fromKeychainForName: "authorization")

GTLRGmailServiceはGmailAPIクエリを実行するための作業人みたいな感じだと思います。
この作業する人が本人かどうか調べます。authorizerに、認証完了している情報をキーチェーンを用いて、GTMAppAuthFetcherAuthorizationを生成して代入する。これは、認証許可している人かどうか調べるためです。

 service.executeQuery( query,
                       delegate: self,
                       didFinish: #selector(callback) )

作業人が本人だったら作業開始します。
その時、callback関数の引数に結果が帰っていきます。
callbackの中身を具体的に見ていきます。

@objc func callback( ticket : GTLRServiceTicket,
                             finishedWithObject response : GTLRGmail_ListMessagesResponse,
                             error : NSError? ){
            if let error = error {
                print("メッセージリストの取得に失敗しました。")
                print(error)
                return
            }
        print("メッセージリストの取得に成功しました。")
        guard let message=response.messages?.first else {return}
        guard let messageid=message.identifier else {return}
        guard let url=URL(string: "https://gmail.googleapis.com/gmail/v1/users/\(userId)/messages/\(messageid)") else {return}
        var urlRequest=URLRequest(url: url)
        guard let token=authorization?.authState.lastTokenResponse?.accessToken else {return}
        urlRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        let session=URLSession.shared
        session.dataTask(with: urlRequest){
            (data,urlResponse,err) in
            if let err=err {
                print("失敗しました\(err)")
            }
            do{
                let responseJson=try JSONDecoder().decode(res.self, from: data!)
                responseJson.payload.headers.forEach {
                    if($0.name=="From"){
                        print($0.value)
                    }
                }
            }catch(let err){
                print("失敗した\(err)")
            }
        }.resume()
    }
if let error = error {
                print("メッセージリストの取得に失敗しました。")
                print(error)
                return
            }

引数のerrorに値が入っていたら、エラーを表示し、returnでcallback関数を抜けます。

guard let message=response.messages?.first else {return}
guard let messageid=message.identifier else {return}

response.messagesは,配列でいーーーっぱい、messageIDとthreadIDが格納されています
今回最初のメッセージが取得したいのでresponse.messages?.firstで配列の最初のメッセージ情報を取得します
ほんでもって,message.identifierでメッセージIDを取得します.

このメッセージIDを用いて,URLを生成します.

guard let url=URL(string: "https://gmail.googleapis.com/gmail/v1/users/\(userId)/messages/\(messageid)") else {return}
var urlRequest=URLRequest(url: url)
guard let token=authorization?.authState.lastTokenResponse?.accessToken else {return}
urlRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

先ほどのmessageidを用いてURLを生成します。
その後、URLRequest型を定義します。
URLRequest型は通信のリクエストを表現する型です。HTTPリクエストのURL、ヘッダ、メソッド、ボディなどの情報を持ちます。
ヘッダにはアクセストークンが必要です。
(urlRequest.httpMethod="GET"を途中に書いても問題ないと思います。)
ヘッダにアクセストークンを追加します。
forHTTPHeaderFieldについては、通信に関するお話でよくわからないです。( i _ i )
また、勉強しておきます。
Appleのドキュメントに詳しく乗っています。

let session=URLSession.shared
session.dataTask(with: urlRequest){(data,urlResponse,err) in
         if let err=err {
                print("失敗しました\(err)")
            }
         do{
            let responseJson=try JSONDecoder().decode(res.self, from: data!)
            responseJson.payload.headers.forEach {
              if($0.name=="From"){
                  print($0.value)
               }
            }
          }catch(let err){
            print("失敗した\(err)")
          }
}.resume()

URLSessionクラスは,URL経由でのデータの送受信に使います。
リクエストはタスクと呼ばれます。
詳しく知りたい方はしたの記事を参考に
【Swift】URLSessionまとめ
URLSession.sharedはURLSessionクラスにデフォルト値が設定されたインスタンスです。

if let err=err {
                print("失敗しました\(err)")
}

通信が完了して、エラーが帰ってきたらエラーを表示します

do{
    let responseJson=try JSONDecoder().decode(res.self, from: data!)
    responseJson.payload.headers.forEach {
         if($0.name=="From"){
                  print($0.value)
         }
     }
}catch(let err){
            print("失敗した\(err)")
}

通信が完了してdataの中に一番最新のメール情報に関するJSONファイルがあるのでパースします。
JsonDecoder().decode()でSwiftの型に変換しています。
今回、誰から連絡きたのかが知りたいのでforEachでループして探しています。。。
resは以下のようです。

struct res: Codable{
    var payload:Playload

    struct Playload:Codable {
        var headers:[Header]
    }
    struct Header:Codable {
        var name:String
        var value:String
    }
}

誰から、連絡きていたのか?を記事に載せようとしたけど、しょーもない人だったのでやめときます。。

感想

初心者なのでわからないことばかりですが、少しでもお力になれれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[iOS14]UITableViewCellに置いたボタンが押せなくなった

かなり昔に書かれたObjective-Cソースコードで、iOS14になってから発生したバグについてです。

何が起きたか

UITableViewCellサブクラスの実装で、コードでレイアウトしていたソースがありました。
こんな感じです。

- (void)awakeFromNib {    
    [super awakeFromNib];

    UIButton *button = [[UIButton alloc] init];
    [button addTarget:self action:@selector(doSomething:) forControlEvents:UIControlEventTouchUpInside];
    [self addSubView: button];

}

このボタンが、iOS14ではなぜか押せなくなりました。

原因

iOS14からは、UITableViewCell.contentView(UITableViewCellContentView)がUITableViewCellの大きさになっており、かつ一番手前に来るようになりました。
なので、上記のソースではUITableViewCell自体にaddSubViewしていたため、UIButtonよりも前面にUITableViewCellContentViewが来てしまい、UIButtonが押せなくなりました。

解決方法

    //[self addSubView: button];
    [self.contentView addSubView: button];

これで押せるようになります。

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】TargetedExtensionとは

はじめに

僕のSwiftで最も好きな文法はExtensionです笑
基礎的なExtensionの使い方はこちらをみてみてください。
TargetedExtensionというものがあるらしいので、みていきましょう。

Extensionとは?

詳細なエクステンションの説明は省きますが、エクステンションは以下のように構造体やクラス、プロトコルを拡張してメソッドなどを追加できます。
例を見てみましょう。

extension Int {
    var minus10: Self {
        return self - 10
    }
    func plus100() -> Self {
        return self + 100
    }
}
print(200.minus10) //190
print(200.plus100().plus100().minus10) //390

こちらも合わせてご覧ください(メソッドチェーン)

TargetedExtensionとは?

前項の例だといいのですが、命名によってはこのメソッドは既にあるものなのか、自分で作ったものなのかわからない場合がありますし、Xcodeの予測変換でメソッドやプロパティが自分で作った数だけ多くなるので、見辛くなってしまうかもしれません。できればXcodeの補完を汚さずに、自作のメソッドなどにアクセスしたいです。
このような時に役に立つのがTargetedExtensionです。
前項のプログラムを修正していきましょう。

protocol SomeComapatible {
    associatedtype ComapatibleType
    var own: ComapatibleType { get }
}

class Some<T> {
    private let base: T
    init(_ base: T) {
        self.base = base
    }
}

extension SomeComapatible {
    var own: Some<Self> {
        return Some(self)
    }
}

extension Int: SomeComapatible { }

extension Some where T == Int {
    var minus10: Int {
        return base - 10
    }
    func plus100() -> Int {
        return base + 100
    }
}

こうすることで、以下のようにownを間に挟まないとminus10プロパティやplus100メソッドにアクセスできないようにできました。

print(1000.minus10) //エラー
print(1000.own.plus100()) //1100

解説

ステップ1

このプロトコルを採用したものはownプロパティを持つようにする。

protocol SomeComapatible {
    associatedtype ComapatibleType
    var own: ComapatibleType { get }
}

ステップ2

Intに先ほどのプロパティを準拠させます。しかし、ownプロパティの実装がありませんので、次で実装しましょう。

extension Int: SomeComapatible { }

ステップ3

以下のように実装します。Some<Self>SelfはプロトコルSomeComapatibleを準拠させた具体的な型なので、今回はIntですね。
そして、return Some(self)selfprint(1000.own.plus100())このように書いた時の1000のことです。
このように、ジェネリクスも用いてあげることで、IntだけでなくStringなどでも同じownを利用する事ができます。

class Some<T> {
    private let base: T
    init(_ base: T) {
        self.base = base
    }
}

extension SomeComapatible {
    var own: Some<Self> {
        return Some(self)
    }
}

ステップ4

では、具体的にメソッドなどを定義していきます。

extension Some where T == Int {
    var minus10: Int {
        return base - 10
    }
    func plus100() -> Int {
        return base + 100
    }
}

このminus10plus100TIntの時のみ使えると言う条件を付け足しました。
baseIntなので、実際は1000などが入ってきます。

おわりに

メソッドチェーンはこのような考慮がかけています。ぜひ、この記事を参考に書き換えてみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter で pod install が失敗する [!] The 'Pods-Runner' target has transitive dependencies that include statically linked binaries

出力されているエラー

$ pod install
Analyzing dependencies
cloud_firestore: Using Firebase SDK version '6.33.0' defined in 'firebase_core'
firebase_analytics: Using Firebase SDK version '6.33.0' defined in 'firebase_core'
firebase_core: Using Firebase SDK version '6.33.0' defined in 'firebase_core'
firebase_messaging: Using Firebase SDK version '6.33.0' defined in 'firebase_core'
firebase_remote_config: Using Firebase SDK version '6.33.0' defined in 'firebase_core'
Downloading dependencies
[!] The 'Pods-Runner' target has transitive dependencies that include statically linked binaries: (/path-to/ios/Flutter/Flutter.framework)

解決方法

今後実行するスクリプトは全てiosディレクトリ内で実行してください

$ cd ios

まずは更地に戻します

$ flutter clean
$ rm -rf Flutter/Flutter.framework Pods Podfile.lock

Podfileに追記します

# NOTE: For detail ? https://github.com/CocoaPods/CocoaPods/issues/3289
pre_install do |installer|
  Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {}
end

スクリーンショット 2021-03-29 18.29.34.png

パッケージを入れ直してビルドします

$ flutter pub get
$ flutter build ios
$ pod install

次のようなログが出ると成功です

$ pod install
Analyzing dependencies
cloud_firestore: Using Firebase SDK version '6.33.0' defined in 'firebase_core'
firebase_analytics: Using Firebase SDK version '6.33.0' defined in 'firebase_core'
firebase_core: Using Firebase SDK version '6.33.0' defined in 'firebase_core'
firebase_messaging: Using Firebase SDK version '6.33.0' defined in 'firebase_core'
firebase_remote_config: Using Firebase SDK version '6.33.0' defined in 'firebase_core'
Downloading dependencies
Generating Pods project
Integrating client project
Pod installation complete! There are 17 dependencies from the Podfile and 39 total pods installed.

間違った解決方法

use_frameworks! をコメントアウトする

スクリーンショット 2021-03-29 18.32.54.png
普通にエラーになります

[!] The 'Pods-Runner' target has transitive dependencies that include static frameworks: (FirebaseCore, FirebaseDatabase, FirebaseMessaging, FirebaseDatabase, FirebaseCore, FirebaseMessaging, FirebaseMessaging, FirebaseCore, and FirebaseDatabase)

cf. https://github.com/flutter/flutter/issues/20045#issuecomment-409492211

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

いい感じのランダムの色が欲しいなぁって時にこれ。

例えば10個のいい感じのランダムの色が欲しいって時。
ガチのランダムにすると意味わからない変な色が出来上がったりしてしまうので。
っていうものをメモ程度に残しておきます。
意外と使える!

hueの部分の分母を欲しい個数にするだけです。
saturationとbrightnessに関しては自分の好みの色味にしてください。

下記コードそのまま使用した際にはこんな感じの色が出力されます。
似てる色があるとか言わないで
#CC8B29
#ABCC29
#49CC29
#29CC6A
#29CCCC
#296ACC
#4929CC
#AB29CC
#CC298B
#CC2929

swift.swift
let colorList = 
   [
    UIColor(hue: 0/10, saturation: 0.8, brightness: 0.8, alpha: 1),
    UIColor(hue: 1/10, saturation: 0.8, brightness: 0.8, alpha: 1),
    UIColor(hue: 2/10, saturation: 0.8, brightness: 0.8, alpha: 1),
    UIColor(hue: 3/10, saturation: 0.8, brightness: 0.8, alpha: 1),
    UIColor(hue: 4/10, saturation: 0.8, brightness: 0.8, alpha: 1),
    UIColor(hue: 5/10, saturation: 0.8, brightness: 0.8, alpha: 1),
    UIColor(hue: 6/10, saturation: 0.8, brightness: 0.8, alpha: 1),
    UIColor(hue: 7/10, saturation: 0.8, brightness: 0.8, alpha: 1),
    UIColor(hue: 8/10, saturation: 0.8, brightness: 0.8, alpha: 1),
    UIColor(hue: 9/10, saturation: 0.8, brightness: 0.8, alpha: 1)
   ]
java.java
    /*
    * いい感じの色のリストを指定数分作成する
    * colorCount: 色の数
    * brightness: 色味 0.0~1.0
    * */
    private int[] createRandomColorList(int colorCount, float brightness) {
        float [] f = new float[3];
        f[1] = brightness;
        f[2] = brightness;
        int[] cList = new int[colorCount];
        for (int i = 0; i < colorCount; i++) {
            f[0] = 360f * (i+1)/colorCount;
            cList[i] = Color.HSVToColor(f);
        }
        return cList;
    }

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift] 大文字のSelfまとめ

はじめに

小文字のselfはよく使いますが、大文字のSelfというのもあります。
大文字のSelfについてまとめてみました。

型の内部で型自身へのアクセス

以下のように、Self.valueで構造体Aに紐付いたvalueにアクセスすることができます。

struct A {
    static let value = 10
    func printValue() {
        print(Self.value)
    }
}

ちなみに、Selfを書かずに直接スタティックプロパティ(value)にアクセスしようとすると
Static member 'value' cannot be used on instance of type 'A'
このようなエラーが出てきてしまいます。

スタティックメソッドでも同様です。

struct B {
    var name: String
    var age: Int
    static func isOld(_ x: Int) -> Bool {
        return x > 60
    }
    func ageFunc() {
        print(Self.isOld(age))
    }
}

ちなみに、Selfを構造体名のBと書き換えても同じことです。ここではSelf == Bですね。

プロトコルの定義での利用

プロトコルはそれに準拠させた型の振る舞いを事前に宣言しておくことができますが、プロトコル自身はどの型で準拠されるのかは知りません。例えるなら、車の大きさ、色、ハンドルの位置などは製造の段階で決める事ができますが、製造の段階では一体この車はどんな人が乗るのかは知りません。利用者(型)が購入(準拠)して初めてどんな人が乗るのか(プロトコル自身がどの型で準拠されるのか)を知る事ができます。
しかし、その型と同じ型のデータを返すメソッドや、同じ型同士を比較したい時もあります。その時にSelfキーワードを使います。

注意点として、前項ではその定義を含む型自身をSelfと置いていましたが、本項では少し扱い方が異なります。つまり、プロトコル定義内でSelfを用いてもプロトコルがSelfというわけではありません。ここでのSelfとは、プロトコルを採用した具体的な型 == Selfです。

protocol C {
    associatedtype Element
    var x: Element { get }
    var y: Element { get }
    func replace() -> Self
    static func +(lhs: Self, rhs: Self) -> Self
}

このように、xとyを入れ替えるreplaceメソッド、左辺の右辺を加算するスタティックメソッドをprotocol Cで宣言しています。replaceメソッドの戻り値にSelfを指定しています。この時点ではどのような型に準拠するのかわからないためです。スタティックメソッド+も同様です。
構造体Dに準拠させてみましょう。

struct D: C {
    typealias Element = Int
    var x: Int
    var y: Int
    func replace() -> D {
        return D(x: y, y: x)
    }
    static func +(lhs: D, rhs: D) -> D {
        return D(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
    }
}

このように、Selfのところが具体的なかたDに変わったのがわかります。このプロトコルCは他の型にも紐付く事ができるようにするため、できるだけ具体的な型はプロトコル宣言時に書きたくないためです。(associatedtypeもそのため)

クラス内での利用

プロトコルとは違い、メソッドの返り値の型としてのみ扱う事ができます。

class E {
    func someFunc() -> Self {
        return self
    }
}

エクステンションでの利用

前述の通り、プロトコルを準拠する型をSelfで参照できますが、一定の条件を満たす型のみプロトコルエクステンションを有効にしたいこともあります。
そのような条件を記述するときにSelfでその型を表す事ができます。

protocol SomeProtocol { }

class SomeClass { }

extension SomeProtocol where Self: SomeClass {
    func someFunc() {
        print(type(of: self))
    }
}

class SomeSubClass: SomeClass, SomeProtocol { }

let someSubClass = SomeSubClass()
someSubClass.someFunc()

この例では、SomeProtocolを準拠する型が条件Self: SomeClassつまり、SomeClassを継承している場合のみsomeFunc()を利用する事ができます。
以下のように、SomeProtocolに準拠していてもSomeSubClass2SomeClassを継承していない(条件を満たしていない)ため、someFunc()にアクセスしようとするとエラーが出てきてしまいます。

class SomeSubClass2: SomeProtocol { }

let someSubClass2 = SomeSubClass2()
someSubClass2.someFunc()

エラー
Referencing instance method 'someFunc()' on 'SomeProtocol' requires that 'SomeSubClass2' inherit from 'SomeClass'

おわりに

selfと違ってSelfはややこしいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】大文字のSelfまとめ

はじめに

小文字のselfはよく使いますが、大文字のSelfというのもあります。
大文字のSelfについてまとめてみました。

型の内部で型自身へのアクセス

以下のように、Self.valueで構造体Aに紐付いたvalueにアクセスすることができます。

struct A {
    static let value = 10
    func printValue() {
        print(Self.value)
    }
}

ちなみに、Selfを書かずに直接スタティックプロパティ(value)にアクセスしようとすると
Static member 'value' cannot be used on instance of type 'A'
このようなエラーが出てきてしまいます。

スタティックメソッドでも同様です。

struct B {
    var name: String
    var age: Int
    static func isOld(_ x: Int) -> Bool {
        return x > 60
    }
    func ageFunc() {
        print(Self.isOld(age))
    }
}

ちなみに、Selfを構造体名のBと書き換えても同じことです。ここではSelf == Bですね。

プロトコルの定義での利用

プロトコルはそれに準拠させた型の振る舞いを事前に宣言しておくことができますが、プロトコル自身はどの型で準拠されるのかは知りません。例えるなら、車の大きさ、色、ハンドルの位置などは製造の段階で決める事ができますが、製造の段階では一体この車はどんな人が乗るのかは知りません。利用者(型)が購入(準拠)して初めてどんな人が乗るのか(プロトコル自身がどの型で準拠されるのか)を知る事ができます。
しかし、その型と同じ型のデータを返すメソッドや、同じ型同士を比較したい時もあります。その時にSelfキーワードを使います。

注意点として、前項ではその定義を含む型自身をSelfと置いていましたが、本項では少し扱い方が異なります。つまり、プロトコル定義内でSelfを用いてもプロトコルがSelfというわけではありません。ここでのSelfとは、プロトコルを採用した具体的な型 == Selfです。

protocol C {
    associatedtype Element
    var x: Element { get }
    var y: Element { get }
    func replace() -> Self
    static func +(lhs: Self, rhs: Self) -> Self
}

このように、xとyを入れ替えるreplaceメソッド、左辺の右辺を加算するスタティックメソッドをprotocol Cで宣言しています。replaceメソッドの戻り値にSelfを指定しています。この時点ではどのような型に準拠するのかわからないためです。スタティックメソッド+も同様です。
構造体Dに準拠させてみましょう。

struct D: C {
    typealias Element = Int
    var x: Int
    var y: Int
    func replace() -> D {
        return D(x: y, y: x)
    }
    static func +(lhs: D, rhs: D) -> D {
        return D(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
    }
}

このように、Selfのところが具体的なかたDに変わったのがわかります。このプロトコルCは他の型にも紐付く事ができるようにするため、できるだけ具体的な型はプロトコル宣言時に書きたくないためです。(associatedtypeもそのため)

クラス内での利用

プロトコルとは違い、メソッドの返り値の型としてのみ扱う事ができます。

class E {
    func someFunc() -> Self {
        return self
    }
}

エクステンションでの利用

前述の通り、プロトコルを準拠する型をSelfで参照できますが、一定の条件を満たす型のみプロトコルエクステンションを有効にしたいこともあります。
そのような条件を記述するときにSelfでその型を表す事ができます。

protocol SomeProtocol { }

class SomeClass { }

extension SomeProtocol where Self: SomeClass {
    func someFunc() {
        print(type(of: self))
    }
}

class SomeSubClass: SomeClass, SomeProtocol { }

let someSubClass = SomeSubClass()
someSubClass.someFunc()

この例では、SomeProtocolを準拠する型が条件Self: SomeClassつまり、SomeClassを継承している場合のみsomeFunc()を利用する事ができます。
以下のように、SomeProtocolに準拠していてもSomeSubClass2SomeClassを継承していない(条件を満たしていない)ため、someFunc()にアクセスしようとするとエラーが出てきてしまいます。

class SomeSubClass2: SomeProtocol { }

let someSubClass2 = SomeSubClass2()
someSubClass2.someFunc()

エラー
Referencing instance method 'someFunc()' on 'SomeProtocol' requires that 'SomeSubClass2' inherit from 'SomeClass'

おわりに

selfと違ってSelfはややこしいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

try Swift:スケジュール画面の解析

はじめに

try Swiftのスケジュール画面を解析する。ゴールは登場するクラスの洗い出しと関連性のざっくりとした把握。

スケジュール画面のスクリーンショット

スケジュール画面の構成及び構成する主なクラス

画面構成

[SessionTableViewCell達]

[SessionTableViewController1, SessionTableViewController2, SessionTableViewController3]

ScheduleViewController

UI部分

ScheduleViewController:スケジュール画面の母体
ButtonBarView:画面上部のバーボタン(ボタンによって画面のコンテンツが表示される)
SessionTableViewController:画面のコンテンツが表示される画面
SessionTableViewCell:スケジュールコンテンツ画面

モデル部分

ConferenceDay:会議日付及びSession情報を含む構造体
SessionBlock:Sessionsのスタート、エンド、Session情報達
Session:Session情報

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む