トップ 追記

Cocoa練習帳

iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど

2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|

2019-10-31 [macOS]zshでgitのブランチ名を表示させる

Mojaveまでは、手動でCommand Line Toolsをインストールしたら設置されるスクリプトを使ってbashでgitのブランチ名を表示させていたが、Catalinaからは設置されないようになったようだ。また、Catalinaからはzshがデフォルト・シェルになったということで、zshでgitのブランチ名を表示させる方法を調べた。

ホームディレクトリ配下に.zshというディレクトリを作って、そこにgit-completion.zshとgit-prompt.shをダウンロードして配置する。

% cd
% mkdir .zsh
% curl https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.zsh -o ~/.zsh/git-completion.zsh
% curl https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh -o ~/.zsh/git-prompt.sh

.zshrcに以下のコードを追加する。

# Git
 
fpath=(~/.zsh $fpath)
 
if [ -f ${HOME}/.zsh/git-completion.zsh ]; then
        zstyle ':completion:*:*:git:*' script ~/.zsh/git-completion.zsh
fi
 
if [ -f ${HOME}/.zsh/git-prompt.sh ]; then
        source ${HOME}/.zsh/git-prompt.sh
fi
 
GIT_PS1_SHOWDIRTYSTATE=true
GIT_PS1_SHOWUNTRACKEDFILES=true
GIT_PS1_SHOWSTASHSTATE=true
GIT_PS1_SHOWUPSTREAM=auto
 
setopt PROMPT_SUBST ; PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '

これで、zshのプロンプトにgitのブランチ名が表示されるようになる。


2019-10-10 [macOS][Catalina][zsh]bashからzshへ移行する

macOS Catalinaのデフォルト・シェルはzshに変わった。でも、以前のバージョンからのバージョンアップに、自動でzshに変わることはない。勝手に変更されると困るので当然だが。

自分は新しいもの好きなので(zshはとても古いが)、zshに移行してみた!

Catalinaにバージョンアップ後、以下のコマンドでzshのパスを確認する。

$ cat /etc/shells
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.
# /bin/false was added for FTP users that do not have a home directory.
 
/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh
/usr/bin/false

以下のコマンドを実行して、ターミナルを再起動する。

$ chsh -s /bin/zsh

次は、設定ファイルの準備だと思う。csh系とBourne Shell系が混じっている。

.zprofile
.zshrc
.zlogin
.zlogout

以前のシェルがBashの場合、極端な話、Bourne Shellと互換があるBashから、Bourne Shellと互換があるZshへの移行なので、設定に基本的なBourne Shellの記述しかしていない場合、以下のようにコピー後に、多少の手直しで、大丈夫。

% cp .bash_profile .zprofile
% cp .bashrc .zshrc

2019-09-11 [cocoa][swift][kotlin]Cocoa.swift 2019-09に行ってきた

会場は池袋コワーキングスペース OpenOffice FOREST、サンシャイン側だ。

_ 発表

「NSTextViewにコマンドパレットをつける」キーボードのみで操作できるように、NSTextViewにコマンドパレットをつける仕組みの発表で、CMD + Lで起動し、出現したNSTextFieldに文字を打つと、関連するメニューやコンテンツが選択できるようになるものだ。

「macOS/iOS/Android Tips枠(その1)」iOS/Androidの新OSの情報や、ストア申請などについて発表された。

「Tweeting(最近のTweet機能の実装方法)」システムやTwitter社からネイティブ実装のためのAPIが提供されなくなった現在にオススメできるTweet機能のiOSとAndroidの実装方法が紹介された。

「macOS/iOS/Android Tips枠(その2)」先日開催されたiOSDC Japanで発表者が興味を持ったものが紹介された。


2019-08-09 [cocoa][swift][Kotlin]Tweeting

システム側でのSNS共有のサポートが終了したり、公式のTwitter Kit SDKのサポートが停止するなどで、スマートフォン・アプリケーションにTweet機能を組み込む方法が変わってきているので、今時点のTweet機能を組み込む方法を調べてみた。

  • ios
    • Social.framework
      iOS11から廃止。
    • Twitter Kit SDK
      2018年10月末でサポート終了。
  • Android
    • Twitter Kit SDK
      2018年10月末でサポート終了。

方向としては、ネイティブ・コード向けライブラリの提供はやめて、Web技術を利用して欲しいということのようだ。

_ iOS

Universal Linksを利用した方法。Twitterアプリケーションがインストールされていない場合はWebブラウザで、インストールされている場合は、Twitterアプリケーションでの投稿となる。

@IBAction func intentTweet(_ sender : Any) {
    let text = "Web Intentの例"
    let encodedText = text.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
    if let encodedText = encodedText,
        let url = URL(string: "https://twitter.com/intent/tweet?text=\(encodedText)") {
        UIApplication.shared.open(url, options: [:], completionHandler: nil)
    }
}

具体的な仕様は以下で説明されている。

このURLに文言をパラメータとして設定してオープンするという仕組みだ。

Twitterアプリケーションがインストールされていない

Twitterアプリケーションがインストールされていない

Twitterアプリケーションがインストールされている

Twitterアプリケーションがインストールされている

iOSの共有機能を提供するUIActivityViewControllerを利用する方法。Twitterアプリがインストールされていないと候補に現れないだとか、SNS共有っぽくはない。でも、テキストに加え、画像も直に共有できる。

@IBAction func activityTweet(_ sender : Any) {
    let text = "共有機能を利用する"
    let bundlePath = Bundle.main.path(forResource: "brownout", ofType: "jpg")
    let image = UIImage(contentsOfFile: bundlePath!)
    let shareItems = [image, text] as [Any]
    let controller = UIActivityViewController(activityItems: shareItems, applicationActivities: nil)
    present(controller, animated: true, completion: nil)
}

共有先を選ぶ

共有機能

Twitterを選ぶと、Twitterアプリの投稿画面。画像も扱える。

共有機能 Twitter

_ Android

ボタンと配置する。最近のAndroid Studioは向上している。Xcode並みに開発しやすくなっている。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
 
    <Button
        android:id="@+id/intentTweetButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="intent tweet"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
 
    <Button
        android:id="@+id/shareCompatButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="ShareCompat"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/intentTweetButton" />
 
</androidx.constraintlayout.widget.ConstraintLayout>

インテントを利用する方法。TwitterアプリがインストールされていないとWebブラウザで、インストールされているとTwitterアプリが利用される。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
 
    val intentTweetButton: Button = findViewById(R.id.intentTweetButton)
    intentTweetButton.setOnClickListener {
        shareTwitter()
    }
}
 
fun shareTwitter() {
    val message = "shareTwitter intent tweet"
    try {
        val sharingIntent = Intent(Intent.ACTION_SEND)
        sharingIntent.setClassName("com.twitter.android", "com.twitter.android.PostActivity")
        sharingIntent.putExtra(Intent.EXTRA_TEXT, message)
        startActivity(sharingIntent)
    }
    catch (e: Exception) {
        Log.e("In Exception", "Comes here")
        val i = Intent()
        i.putExtra(Intent.EXTRA_TEXT, message)
        i.action = Intent.ACTION_VIEW
        i.data = Uri.parse("https://mobile.twitter.com/compose/tweet")
        startActivity(i)
    }
}

iOSのWeb Intentと同様な仕組みだ。

intent tweet

共有機能を利用する方法。TwitterアプリがインストールされていないとTweetできない。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
 
    val shareCompatButton: Button = findViewById(R.id.shareCompatButton)
    shareCompatButton.setOnClickListener {
        shareCompat()
    }
}
 
fun shareCompat() {
    val message = "shareCompat"
    val builder = ShareCompat.IntentBuilder.from(this) 
    builder.setChooserTitle("Choose App")
    builder.setText(message)
    builder.setType("text/plain")
    builder.startChooser()
}

共有先を選ぶ

shareCompat

Twitterを選ぶと、Twitterアプリの投稿画面。画像も扱えるはずだが、自分はうまくいかなかった。

shareCompat tweet

2019-07-20 [cocoa][swift]NSUndoManager

NSUndoManagerの利用は、Swiftで楽になったと思うが、その仕組みが見えにくくなったと思うので、Objective-Cの場合から説明する。

CocoaのUndoとRedoは、NSInvocationというクラスでNSObjectの子クラスとメソッドを保持し、それをNSUndoManager内のスタックで管理することで実現している。

なんらかの操作を行うと、Undoに必要なNSInvocationのインスタンスがUndoスタックに積まれていく。

undo stack

ユーザがUndoを行うと、Redoに必要なNSInvocationのインスタンスがRedoスタックに積まれていく。

redo stack

Objective-Cのコードで、以下のようになる。

- (void)makeItHotter
{
    temperature = temperature + 10;
    [[undoManager prepareWithInvocationTarget:self] makeItColder];
}
 
- (void)makeItColder
{
    temperature = temperature - 10;
    [[undoManager prepareWithInvocationTarget:self] makeItHotter];
}

makeItHotterで温度を10度上げて、makeItColderをNSUndoManagerに積む。

makeItColderでは、温度を10度下げて、makeItHotterをNSUndoManagerに積む。

これをSwiftを書く場合、積むメソッドのselectorを用意するのが面倒になるのだが、selectorを必要としないメソッドが用意されていた。以下のようになる。

func makeItHotter() {
    var temperature = self.textField.intValue
    temperature = temperature + 10
    self.undoManager?.registerUndo(withTarget: self, handler: {
        vc in
        vc.makeItColder()
    })
    self.textField.intValue = temperature
}
 
func makeItColder() {
    var temperature = self.textField.intValue
    temperature = temperature - 10
    self.undoManager?.registerUndo(withTarget: self, handler: {
        vc in
        vc.makeItHotter()
    })
    self.textField.intValue = temperature
}

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/mac/Temperature - GitHub

2019-07-15 [cocoa][swift]【研究】スパゲッティ・コード誕生の理由(わけ)

自分はスパゲティ・プログラムには二種類あって、一つは未熟だから、もう一つは経験も技術もあるのに、と考えている。そして、問題は後者の方で、考えに考え抜いたスパゲティ、理由を聞いても皆が納得するないようで、本人も信念を持って取り組んだ結果だから、それを何とかするのが非常に困難だと考えている。

今回、別件でワインバーグ氏について調べていたら、スパゲティ・プログラムの原因を見つけるヒントが目に留まったので、それと、発見した原因を発表する。

_ よく適応すれbするほど、適応力を失いがちだ。

『コンサルタントの秘密 - 技術アドバイスの人間学 -』で紹介されていて、生物学の分野でロナルド・フィッシャー卿の「フィッシャーの基本定理」と呼ばれるもので、自然淘汰に関する基本定理だ。

ワインバーグ氏の著書では、プログラミングに関して取り上げた定理でないが、この意味を考えてみた閃いた。

経験のある、プロジェクトでも声がでかい人間が生み出すスパゲッティ・プログラム。それは、今、この瞬間に最適なもので、そのため、将来の変化に対応できず、それが、のちの人を苦しめている。

具体的な例を挙げてみよう。

例えば、三つのテーブル・ビューがあるスマートフォン・アプリケーション。一つは新規リスト、もう一つは、編集リスト、最後は削除リスト。リストを管理して、表示するという部分のコードは共通化できる。ベースクラスを導入しよう、と、スパゲティ・プログラム料理人は考える。

class BaseTableViewController {
    func sortList() {
        ....
    }
}

よし、うまくいった。これがリファクタリングだ!俺ってすごい。

ここで悲しい依頼が。新規リストは、条件によって並び替えのルールを変えて欲しい。ああ、どうしよう。そうだ、前処理関数を導入すればいい!

class BaseTableViewController {
    func prepare() {
        ...
    }
    
    func sortList() {
        prepare()
        ...
    }
}
 
class NewTableViewController {
    override func prepare() {
        ...
    }
}

新規リストのクラスで、前処理関数をオーバーライドして、挙動を変えればいい!

こうして、また新たなスパゲティ・プログラムが生み出される!

基本クラスのコードを読んでも、新キリストのコードを読んでも、どんな動きをするのか読み取れない。

では、どうやって、スパゲティ・プルグラムの調理が行われているのを見つけるのか?

長年の研究によって、自分が見つけたのは、以下だ。

  • これはリファクタリングした方がいいな。やろう!
    今この瞬間の共有に適応した、将来の適応力がないコードを生産する掛け声だ!
  • このコードは安全でないので修正する!
    これからプログラミングされる部分は安全であるように心がけることには意義がないが、既に生産されたコード。そして、それが今後も手を加える可能性が低い箇所だとしてら、そいつが単に気に食わないから修正するということの言い訳だ。そんな奴に贈るワインバーグ氏の言葉はこれだ。『壊れていないものを直すな。』

2019-07-14 [cocos2d-x]OpenGL ESからMetalへの移行

iOSもAndroidも、OpenGL ESから次の新しい描画エンジンへの移行を進めている。

Androidについては、GoogleがVulkan上にOpenGL ESを実装して、OpenGL ESの提供は続くようなので安心なのだが、iOSについては、AppleがOpenGL ESを非推奨となっているので、今後、利用できなくなっても不思議ではない。

そこで、cocos2d-xを利用している場合が対応方法を調べてみた。

_ MoltenGL

macOSとiOSでVulkanのAPIを提供するMoltenVKを提供したMoltenの製品で、Metal上にOpenGLとOpenGL ESのAPIを提供している。

MoltenVKは、Khronos Groupと共同でオープンソースとなっていて、cocosのフォーラムでも協力者がいればMoltenGLのオープンソース化も検討していそうだ。

ただ、MoltenGLで、問題なく、OpenGL ESを利用しているアプリケーションがMetal対応になるかは、やってみないとわからないと思う。

_ cocos2d-x metal-supportブランチ

cocs2d-x v3.17から派生したmetal-supportブランチで、Metal対応が進んでいる。

GitHubのIssuesでのやり取りも活発なので、metal-supportブランチが安定して成長するよう、一緒に貢献していくというのもありだと思う。


2019-07-13 [cocos2d-x][OpenGL ES]画面キャプチャ

cocos2d-x v2には、画面キャプチャの機能が用意されていないので、実装した。

ただし、内容には満足していない部分があって、例えば、使用しているOpenGL ESのAPIは呼び出しコストが高いという情報があるのと、フレームバッファの解像度が高ければ高いほどメモリを使用するので、機会があれば改善したい。

/*!
 @brief Screenshotクラス
 */
class MyGrab {
public:
    /*! @brief デストラクタ */
    virtual ~MyGrab();
 
    /*!
     @brief Singletonなインスタンスを返す。マルチスレッドを考えるの、この実装では不受分なのは認識している。
     @result BQGrabのインスタンス
     */
    static MyGrab* getInstance();
 
    /*!
     @brief Screenshot
     @result 画像
     */
    cocos2d::CCImage* grab();
 
private:
    /*!
     @brief コンストラクタ
     @discussion Singletonとする為、コンストラクタは非公開メンバ関数とする。
     */
    MyGrab();
 
    /*!
     @brief 初期化。
     @retval true 成功
     @retval false 失敗
     */
    bool initialize();
};
 
MyGrab::MyGrab()
{
    /* 初期化 */
    initialize();
}
 
MyGrab::~MyGrab()
{
}
 
MyGrab* MyGrab::getInstance()
{
    static MyGrab grab;
    return &grab;
}
 
bool MyGrab::initialize()
{
    return true;
}
 
CCImage* MyGrab::grab()
{
    /* EGLビューのフレーム・サイズを取得する */
    const unsigned int width = (unsigned int)CCDirector::sharedDirector()->getOpenGLView()->getFrameSize().width;
    const unsigned int height = (unsigned int)CCDirector::sharedDirector()->getOpenGLView()->getFrameSize().height;
 
    const unsigned int bytesPerPixel = 4; /* RGBA */
    GLubyte* dataBuffer = NULL;
    dataBuffer = (GLubyte*)malloc(width * height * bytesPerPixel);
 
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dataBuffer);
 
    GLubyte *imageBuffer = NULL;
    imageBuffer = (GLubyte*)malloc(width * height * bytesPerPixel);
    for (int i = 0; i < height; ++i) {
        memcpy(&imageBuffer[i * width * 4], &dataBuffer[(height - i - 1) * width * 4], width * 4);
    }
 
    CCImage* image = new CCImage();
    image->initWithImageData(imageBuffer, width * height * bytesPerPixel, CCImage::kFmtRawData, width, height, 8);
    free(dataBuffer);
    free(imageBuffer);
    image->autorelease();
    return image;
}

glReadPixels()はフレームバッファの内容をピクセルデータとして読み取るコマンドだ。

void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *data);

フレームバッファからウィンドウ座標xとyを左下隅としてwidth × heightのピクセル矩形を読み込み、dataで指定されたアドレスに格納する。formatパラメータは、そのピクセルが何を表しているかをOpenGLに伝える。typeパラメータは、ピクセルのデータ型を示す。


2019-06-07 Google I/O Extended: Recap Live Japan 2019

5/7〜9にかけてマウンテンビューで開催された『Google I/O 2019』の内容をダイジェストで伝えると共に、日本のGoogle Developer Advocateの方々に直接質問できる『Google I/O Extended: Recap Live Japan 2019』が6/7にGoogle東京オフィスで開催されたので、その中から、自分が気になって項目をピックアップした。

_ テーマ

building a more helpful Google for everyone
すべての人々のためにより役立つGoogleへ

_ Playストア

利用者が未インストールがインストールしたことがある、インストール済みなのかが分かるので、それに対応した表示を用意できる。

イベント告知ができる

_ App Bundle

App Bundleを推奨するが、デバッグ用に実機転送するため、apkは必要だね。

_ Firebaseアナリティクス

Firebase 向け Google アナリティクスと名称が変わってる。また、Google アナリティクス開発者サービス SDKが終了しているので、スマートフォンでは、Firebaseアナリティクスを利用することになる。

_ マテリアルシーミング

マテリアルデザインからマテリアルシーミングへ。

_ ダークテーマ

ラウンチャー上のアイコンは、ダームモード用を用意する必要はない。例えば、使用している色がロゴと関係あるなど、変更が難しいと思うので。

_ Java 8

Android StudioにOpenJDKが組み込まれているので、問題はないと認識している。

_ targetSDK

OSがリリースされたら、一年後、新規アプリはtargetSDKでの設定が必要。既存アプリはプラス一ヶ月猶予がある。


2019-05-30 [cocoa][swift]よく利用しているサイト

みなさんも、よく利用するサイトがあると思う。自分の場合は以下だ。

Bourne Shell自習テキスト

文字エンコードはJIS。必要最小限度のHTMLで記述。内容も基本的なこと。

でも、自分はずっと重宝しています。20年近く利用しているのでは?

ちょっと変わったLisp入門

数式処理システムに脅威がある、そこからLISP実装に興味があります。

ここの情報を参考に、Objective-CやSwift、C++などで何度実装したか。

実践!iOSで作るゲームアプリ

iOSのViewと画面管理についても参考になりました。


トップ 追記