フリープログラマー日記

iOS,アンドロイド開発を気ままにしながら生きてるおじさんのブログです。

第44回 プロパティとフィールド、よくわかりません。

実はハマりまくって、ブログ更新がおろそかに・・・

そもそも、

Kotlinのクラスは、フィールドを持つことができません。(引用1)

この一文が混乱の元になりました。

じゃあ、フィールドって何?ってなると、Java解説本では

クラスの内部の定義した変数をフィールドと呼びます。(引用2)

さらに、swift解説本では

プロパティとは、クラスや構造体に所属する変数のことです。(引用3)

私ゃ日本人なんで、クラスの中には変数や関数が定義できて変数の処理するのに関数をつかんだな〜ぐらいにしか認識してないのに、この絶望的な矛盾はなんなんだろ?

いや、引用2と引用3はいいんです。それぞれの言語で、呼び名が違うくらいはいいんです。

じゃ、Kotlinで書いたこのクラスでは

class Member(private val myId: Int) {
    // 選手名
    var name: String? = null
}

この後、名前を読み出す関数とかあるのですが、このnameはクラス内の変数に違いないが、同じ書き方であるにも関わらず、Javaでフィールドと呼び、Kotlinではプロパティと呼び、フィールドではないということらしい。

素人なもので、まあいいか〜みたいなスルーでもよかったのですが、読んでくださる方もいるわけで、調べようかと思ったらとんでもないことになりそうです。

Kotlinでは

    member.name = "青木"

とすると、これは、セッターを呼び出していることになるようなんですね。つまり、直接、インスタンス変数にアクセスしていないということを言いたいようなんです。

Javaでは直接、フィールド変数にアクセスできるようで、カプセル化のため、フィールド変数をprivateにし、getter,setterを自分で書くことが多いようです。

あと、Kotlinではバッキングフィールドなる言葉が出てきて、何かしらフィールドが作られてるようなんですが・・・

本音を言ってしまえば、同じ書き方して、同じ動作をすればそれでいいんじゃないっていうところ。

ただ、人に説明する時は、内部でやってることは少し(かなり)違うんですよって付け加える必要がありそうです。

Kotlinまだ麓でウロウロってとこです。

第43回 KotlinでコンパイラWarningが出てくる

Kotlin でお遊びしてて気づいた。
いつも Warningが表示されています。
何も手を加えていない最初のプログラムをコンパイルした時の結果がこれ。


f:id:momonga117:20180620021110p:plain

勝手に作っておいて、どうしてコンパイラエラーなんか出すの!
と、言っててもしょうがないので、原因を探ると・・・

f:id:momonga117:20180620021458p:plain

implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" のところに何やら横線が引かれているじゃありませんか!

さらに。
f:id:momonga117:20180620021758p:plain

ライブラリファイルなんでしょうか?これ。

"org.jetbrains.kotlin:kotlin-stdlib-jre7のところを

org.jetbrains.kotlin:kotlin-stdlib-jdk7に変えろということらしいです。

で、取り替えて見たら、コンパイラエラーは消えました。

でもさ〜。親切にヒントくれるくらいなら、どうして最初からjdkの方にしてくれないのって思ってしまった。


というところで、
Kotlin始めて分かってきたことを2、3書くと。

1.クラスの外側に関数を置くことができる!
(Kotlinできる、Javaできない、Swiftできる)

これは、関数もクラスと同等のオブジェクトであるということらしい。しかし、Swift もできたとは驚き。
これの使用法については、考え中。

2.pair や triple が使える!
例えば、判定結果として、(x、y)の2つの数字を返すような関数を作る時、swiftで

       func Jadge(x: CGFloat, y: CGFloat) -> (i,j) {
         let i = x / 40
         let j = y / 40
         return (i,j)
      }

みたいなことをしていました。
swiftではタプルという仕組みらしいですが、Javaにはなく、最近は使っていませんでした。
Kotlinで使えるなら、また使って見たいなと思っているところです。

swift と Kotlin が似てるようで、微妙に違うのがかえって混乱を起こすことがあります。nil と null は似てるが、swiftの場合、必ず、?か!をつけますよね?
ところが、Kotlinは宣言時に?をつけるところは同じなのに、何もつけないで使ってたり、!!をつけてみたり。

迷っているのは、タッチイベントの時、event: MotionEvent? で渡ってくるので、このeventの扱いが悩みで・・・

    override fun onTouchEvent(event: MotionEvent?): Boolean {

            if (event != null) {
                val touch = event.action      // タッチの種類を格納する
                val x = event.x
                val y = event.y

                when (touch) {
                    MotionEvent.ACTION_UP -> {
                        performClick()
                        val width = width.toFloat()
                        val height = height.toFloat()


                        // 4種の判定
                        // 4コマンド(エントリー・設定・ラップ計測・記録)
                        val y1 = height / 2 - width * 0.4f
                        val y2 = height / 2 - width * 0.3f
                        val y3 = height / 2 + width * 0.3f
                        val y4 = height / 2 + width * 0.4f
                        val x1 = width * 0.05f
                        val x2 = width * 0.3f
                        val x3 = width * 0.7f
                        val x4 = width * 0.95f
                        // 判定
                        if (y > y1 && y < y2) {
                            if (x > x1 && x < x2) {
                                // エントリーメニューの呼び出し
                                entryViewRunnable!!.run()
                            } else if (x > x3 && x < x4) {
                                // 設定メニューの呼び出し
                                settingViewRunnable!!.run()
                }
            }
        return true
    }

このeventは null ではないはずです。多分・・・
でも、教科書的に、null の時は処理しないようにしました。
そしたら、event変数のところにメッセージが???

Smart cast to android.view.MotionEvent ← 何これ?って感じです。
MotionEventについてスマートキャストしてるよ。で、いい?
警告ではなさそうなので、このメッセージが出てもいいのかな。

まだまだ、Kotlin 見えてこないです。Kotlin霧中。(ダジャレ〜、わかるかな?)

ただ、シングルトンに関しては結構簡単に実現できそうなので、楽かも。

swift <-> Kotlin の移植性高いと嬉しいが、どうなんだろね。

第42回 Kotlin に static ないって、知ってた?

Kotlinさわって、1週間。

衝撃的だったのは、staticキーワードがないこと!
まあ、static あるかないかで言語を分ければ、きっとないほうが多いのだろう。(調査する気はありません)

ま、そこは、objectキーワードでなんとかしようかなと思ってますが、Kotlinを使っていこうかどうか検討中にちょっと便利だなと思った書き方がこれです。

val s = (1000/4).toDouble()

単に型変換をするだけですが、評価すべき式を()に入れて、 .to って打つと、.toDouble()だけでなく、いっぱい候補が出てきて、型変換が楽なのだ。

f:id:momonga117:20180616125734p:plain

あと、

Class の継承やってる時に出てきた書き方。

class MainMenu(context: Context) : View(context) {

Mainmenuクラスに渡されたコンテキストを継承元のViewクラスにそっくり渡しているのですが、面倒だなって思った反面、javaではあえて

public class MainMenu extends View {    
    // コンストラクタ
    public MainMenu(Context context) {
        super(context);
    }

というように式を書く必要があったので、楽かもしれない。

java と Kotlin、結論を出すのはまだ早いような気がします。
ま、googleの方針では、Kotlinにシフトしそうなので、Kotlin無視できないなと感じているところなんですが。

私の感覚です。異論は多々あると思いますが。

Java は、3つの中では、一番泥臭い。nullの扱いとか、暗黙の型変換とか。
あと、セミコロンが必要ってとこも、面倒かな。
Kotlin は、今回も触れたように static がないのが良くも悪くも大きな特徴で、null の扱いはswift の nil に似てるといえば似ているのかな。
(null は nil に似る・・・オヤジかー)

static をバンバン使ったswift プログラムから Kotlin への移植はちょっと困るかもしれない。
こういうケースはjava ですかね〜。

swift は外しようがないので、受け入れるしかないです。
私、obj-cやってないので。

当分、この3つを比較しながら、記事を書いていこうと思っています。

第41回 そろそろ、Kotlin?

 ふと、手にした本が、Kotlin書だったために、ブログの更新も忘れて、読みふけってました。
 Kotlin使って見たいけど、Kotlinの情報は少ない。
私にとってもメリットは、javaーswift の差よりも、kotlinーswiftの差の方が近いのではというその一点だけだったのですが。

例えば、
swift (変数名):(変数の型)
java (変数の型) (変数名)
kotlin(変数名):(変数の型)
ご覧のように、javaだけが反対なんですから。


次のようなjavaファイルをそのまま、kotlinファイルにコピペしてみました。

class MySize {

    // このクラスにはいろいろなビューのサイズを保管する

    //基本の画面サイズ
    public static int screenWidth;
    public static int screenHeight;

    // 初期化の必要なのはこの2つだけ
    public static void setWidth(int width) {
        screenWidth = width;
    }
    public static void setHeight(int height) {
        screenHeight = height;
    }

    // 各種パーツの長さ

    // 文字サイズ
    // TypedValue.COMPLEX_UNIT_PX で指定する
    public static class TextSize {
        // テキストサイズ
        static float normal = (float) screenWidth * 0.08f;
        static float small = (float) screenWidth * 0.05f;
    }


ペーストの直後、kotlinに変換しますか?みたいなメッセージが出て、そのままOKするとできたのが、次のファイル。

internal object MySize {

    // このクラスにはいろいろなビューのサイズを保管する

    //基本の画面サイズ
    var screenWidth: Int = 0
    var screenHeight: Int = 0

    // 初期化の必要なのはこの2つだけ
    fun setWidth(width: Int) {
        screenWidth = width
    }

    fun setHeight(height: Int) {
        screenHeight = height
    }

    // 各種パーツの長さ

    // 文字サイズ
    // TypedValue.COMPLEX_UNIT_PX で指定する
    object TextSize {
        // テキストサイズ
        internal var normal = screenWidth.toFloat() * 0.08f
        internal var small = screenWidth.toFloat() * 0.05f
    }

    // アプリ共通の線幅
    object LineWidth {
        // 線幅
        internal var normal = screenWidth / 100
        internal var thin = screenWidth / 150
        internal var thich = screenWidth / 80
    }

何がわかったかというと、javaではスタティックなクラスしか使えなかったのが、親切な変換をしてもらったら、classからobjectに変わってた。
static もきれいに排除されています。


で、よくよく、object構文を見てみると、もしかして、swiftのstructに当たるのでは?と思い始めたわけなんです。

swiftのstructは使ったことないです。解説を読んでみると、class も struct もインスタンスを作ることができるそうです。だから、structなくてもプログラム組めるじゃんって、無視してきました。でも、swift-structはもしかしたら、staticなクラスから、構造体への置き換えで使えるんじゃって思って、今、色々実験中です。案外うまくいくかもしれません。しばらくは、Kotlin でこんなことできた、こんなことしたよって、記事を書いてみたいと思います。

第40回 昔はサブルーチン、今は継承です。

 あるクラスの処理と同じことを別のクラスにも書くってよくありますよね。
2つや3つなら問題ないのでしょう。きっと。
 昔、BASICをさわった人ならわかるでしょうが、GOSUBなんてキーワードがありました。今でも、クラス内の処理で関数を作って呼び出すのは、GOSUB感覚ですね。(この文章のターゲットは明らかにBASIC世代です。すみません。)

 で、別クラスの処理が一部同じものだったら、どうしてますか?単純な方法なら、コピペで同じ処理を書いておく・・・ですよね?

f:id:momonga117:20180610114618p:plain

前回の、氏名登録画面ですが、選手クラスのオブジェクトが5個(Entry = true なら黄色背景)、そして、氏名登録ボタンは別クラスです。

実は、枠線部分の描画ルーチンはコピペしてます。

今のままで、動きます。なんの問題もないです。

ちょっとだけ、コード部分を。

import UIKit

class RunnerEntryView: UIView {
    
    /***
     * エントリー用のビュー一人分を作成する
     * 一人につき1個のビューが必要となる。
     */

    
    let myNumber: Int
    private var myName: String        //氏名表示
    private var entry: Bool = false  // 表示用
    
    // Runnerクラスへのアクセス’Handler
    private var entryOk: (() -> Void)?
    private var entryCancel: (() -> Void)?
    
    // 自前のイニシャライザ
    init(number: Int, name: String) {
        myNumber = number
        myName = name
        
        super.init(frame: MySettings.Entry.runnerEntryViewFrame(num: number))
        setBackGroundColor()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // ハンドラーのセッター
    public func setEntryOk(handler: @escaping () -> Void) {
        self.entryOk = handler
    }
    
    public func setEntryCancel(handler: @escaping () -> Void) {
        self.entryCancel = handler
    }
    
    private func setBackGroundColor() {
        // 背景色をセット、エントリー時には色を変える
        if entry {
            backgroundColor = MyColor.selectYellow
        } else {
            backgroundColor = MyColor.unselectedGrey
        }
    }
    
    override func draw(_ rect: CGRect) {
        
        let myWidth = MySettings.Entry.runnerErentryViewWidth()
        let myHeight = MySettings.Entry.runnerErentryViewHeight()
        let lineWidth = myWidth * 0.01
        
        // 番号枠,氏名枠の幅計算
        let numberWidth = myWidth * 0.22
        let nameWidth = myWidth - numberWidth - lineWidth * 3
        
        // コンテキストの取得
        let context = UIGraphicsGetCurrentContext()
    
        // 両サイド
        context?.setLineWidth(lineWidth)
        context?.move(to: CGPoint(x: lineWidth/2, y: 0))
        context?.addLine(to: CGPoint(x: lineWidth/2, y: myHeight))
        context?.move(to: CGPoint(x: myWidth-lineWidth/2, y: 0))
        context?.addLine(to: CGPoint(x: myWidth-lineWidth/2, y: myHeight))
        context?.strokePath()
        
        // 上下サイド
        context?.setLineWidth(lineWidth*0.3)
        context?.move(to: CGPoint(x: 0, y: lineWidth*0.15))
        context?.addLine(to: CGPoint(x: myWidth, y: lineWidth*0.15))
        context?.move(to: CGPoint(x: 0, y: myHeight - lineWidth*0.15))
        context?.addLine(to: CGPoint(x: myWidth, y: myHeight - lineWidth*0.15))
        context?.strokePath()
        
        // 見た目が悪いので、最初の分だけ上の線を太く。
        if myNumber == 1 {
            context?.setLineWidth(lineWidth*0.8)
            context?.move(to: CGPoint(x: 0, y: lineWidth*0.4))
            context?.addLine(to: CGPoint(x: myWidth, y: lineWidth*0.4))
        }
        // 中間の仕切り線
        context?.setLineWidth(lineWidth*0.6)
        context?.move(to: CGPoint(x: numberWidth, y: 0))
        context?.addLine(to: CGPoint(x: numberWidth, y: myHeight))
        context?.strokePath()
        
        // 番号の描画
        let textcolor = myName=="未登録" ? UIColor.lightGray : UIColor.black
        let numCenter: CGPoint = CGPoint(x:numberWidth*0.5, y: myHeight*0.5)
        centerString(string: String(myNumber), charSize: myHeight*0.5, center: numCenter, color: textcolor)
        
        // 氏名の描画
        let nameCenter: CGPoint = CGPoint(x: numberWidth + lineWidth + nameWidth * 0.5, y: myHeight*0.5)
        centerString(string: myName, charSize: myHeight*0.6, center: nameCenter, color: textcolor)
    }

    func centerString(string: String, charSize: CGFloat, center: CGPoint, color: UIColor) {
        // 文字列、文字サイズ、中央を示す座標、
        let stringAttributes: [NSAttributedStringKey : Any] = [
            NSAttributedStringKey.foregroundColor : color,
            NSAttributedStringKey.font : UIFont.systemFont(ofSize: charSize)
        ]
        let nsstr = string as NSString
        // サイズを取得
        let textSize: CGSize = nsstr.size(withAttributes: stringAttributes)
        let textTopLeft = CGPoint(x: center.x - textSize.width/2, y: center.y - textSize.height/2)
        // 文字列の描画
        nsstr.draw(in: CGRect(origin: textTopLeft, size: textSize), withAttributes: stringAttributes)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if entry {
            entry = false
            if entryCancel != nil {
                entryCancel!()      // Runnerクラスへのアクセス
            }
        } else {
            entry = true
            if entryOk != nil {
                entryOk!();  // Runnerクラスへのアクセス
            }
        }       
        setBackGroundColor()
    }
}
class AddNameButton: UIView {
    
    /**
     * スクロールビューにのせるパーツ
     * タップされた時の動作は、EntryViewに書く
     */
    
    // 氏名入力起動のためのハンドラー
    private var nameInputHandler: (() -> Void)?
    
    override init(frame: CGRect) {
        
        super.init(frame: frame)
        
        backgroundColor = UIColor.white
    
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // ハンドラーのセット
    func setnameInputHandler(handler: @escaping () -> Void) {
        self.nameInputHandler = handler
    }
    
    override func draw(_ rect: CGRect) {
        
        let myWidth = MySettings.Entry.runnerErentryViewWidth()
        let myHeight = MySettings.Entry.runnerErentryViewHeight()
        let lineWidth = myWidth * 0.01
        

        
        // コンテキストの取得
        let context = UIGraphicsGetCurrentContext()
        
        // 両サイド
        context?.setLineWidth(lineWidth)
        context?.move(to: CGPoint(x: lineWidth/2, y: 0))
        context?.addLine(to: CGPoint(x: lineWidth/2, y: myHeight))
        context?.move(to: CGPoint(x: myWidth-lineWidth/2, y: 0))
        context?.addLine(to: CGPoint(x: myWidth-lineWidth/2, y: myHeight))
        context?.strokePath()
        
        // 上下サイド
        context?.setLineWidth(lineWidth*0.3)
        context?.move(to: CGPoint(x: 0, y: lineWidth*0.15))
        context?.addLine(to: CGPoint(x: myWidth, y: lineWidth*0.15))
        context?.move(to: CGPoint(x: 0, y: myHeight - lineWidth*0.15))
        context?.addLine(to: CGPoint(x: myWidth, y: myHeight - lineWidth*0.15))
        context?.strokePath()
        
        // ボタン面
        let nameCenter: CGPoint = CGPoint(x: myWidth * 0.5, y: myHeight*0.5)
        centerString(string: "氏名登録 >>>", charSize: myHeight*0.5, center: nameCenter, color: UIColor.black)
        
    }

(ちょっとと言いながら長いです・・・すみません)

コピペがありありとわかりますよね。

でも、継承を使うとコピペ部分がいらなくなります。

この2つのクラスはUIViewを継承してるんですが、それなら、UIViewを継承した枠線だけを描く中間クラスを作り、そのクラスを継承してやれば、どちらのクラスにも、枠線を描くコードは不要になりますよね?
イメージとしては次のような感じです。

f:id:momonga117:20180610121815p:plain

まあ、できちゃったものをあえて書き換える必要はないんですが、継承の具体的な例として書いてみました。

以前作ったアプリで、6個くらいの縦につながった継承クラスを作りましたが、あれはやりすぎだったかなと反省。

でも、うまく継承を使うと、スッキリするかもです。
 

第39回 ソフトウェアキーボード・・・出ないよ〜(iOS)

最初は大丈夫だったんです。
TextFieldにbecomeFirstResponderを当てておくと、キーボードがでてきて、よかったって済ませてました。ところが・・・

f:id:momonga117:20180608212630p:plain

先ほどより、左の状態に!
何もいじってないし、実機でやると問題ない。iPad Airなどのシミュレータもちゃんとでてる。

そういえば、なんかAppleからUpdateのお知らせがあって、シミュレータも最新版にしたかもって思って、調べてみました。

問題はシミュレータ設定にありました。

f:id:momonga117:20180608213210p:plain

コネクト ハードウェア キーボード?

要するにMacのキーボードと接続するよって設定らしい。
これを外すと、右側のように元に戻りました。

突如変わったもので、あたふたしました。

なお、この設定は機種ごとに別々に設定するみたいです。


やっと、名前の登録ができるところまできました。
左が名前の登録中、右が登録後です。

f:id:momonga117:20180608214319p:plain

第38回 Arrayとforのいい関係(swift & java)

またまた、小ネタですが。

swiftにもjavaにもとても便利で、似た仕組みがあったので報告です。

まず、Arrayの宣言!

swift版

import UIKit
class Team {
    // static でアクセスできるように変更
    // ランナーの所属に関する処理

    static private var lastId: Int = 0     // メンバーIDの最後
    static var runners: Array<Runner> = []

java

class Team {
    // static でアクセスできるように変更
    // ランナーの所属に関する処理

    static private Context myContext;
    static private int lastId = 0;      // 登録メンバー数
    static ArrayList<Runner> runners = new ArrayList<>();

最後の行が宣言部分ですが、swiftではArray、javaではArrayListで宣言します。

次にこの操作。
swift版

runnerList = UIScrollView(frame:MySettings.Entry.entryScrollViewFrame())
        runnerList.backgroundColor = MyColor.darkBlue
        if Team.runners.count != 0 {
            for r: Runner in Team.runners {
                let view: RunnerEntryView = r.makeRunnerEntryView()
                runnerList.addSubview(view)
            }
        }

java

         runnerList = new RunnerListView(getContext());
         runnerList.setBackgroundColor(MyColor.darkBlue);
        if (Team.runners.size() != 0) {
            for (Runner r : Team.runners) {
                RunnerEntryView view = r.makeRunnerEntryView();
                runnerList.AddRunner(view);
            }
        }

ぜひぜひ、このfor文にご注目。
Array使ったら、このfor文の使い方で決まりでしょ。

もう、数字で回すようなfor文は書きたくないですね。
FOR I=1 TO 10 なんてコマンドを打ってたのは遥か昔です。

第37回 swift、javaでMyColorファイルを作る

 誰も教えない便利な技を自分で見つけて発信?ちょっと大げさでしょうか。

 アプリ完成間際になって、色の調整とかってすることありますよね。あるいはサイズの調整とか、フォントいじったりとか。

で、そんな時、どこだったかなと、あちこちのファイルを探しまくるっていう、素人だったんですね、私は。

例えば、色なんかは、まとめて管理した方が楽です。
今回作った、MyColorファイルをご覧ください。って、まだサンプル程度ですが。

Mycolor.swift

import UIKit

// アプリ専用のカラー定義
class MyColor {
    
    // 背景色に使用
    static let lightBlue: UIColor =
        UIColor(red: 100/255, green: 240/255, blue: 255/255, alpha: 255/255)
    
    // 選択メンバー枠の背景
    static let lightAcuaBlue: UIColor =
        UIColor(red: 150/255, green: 255/255, blue: 255/255, alpha: 255/255)
    
    // ランナーリストの背景
    static let darkBlue: UIColor =
        UIColor(red: 50/255, green: 155/255, blue: 155/255, alpha: 255/255)
    
    // ?
    static let lightSkyBlue: UIColor =
        UIColor(red: 200/255, green: 245/255, blue: 255/255, alpha: 255/255)
    
    // ランナーリスト非選択時
    static let unselectedGrey: UIColor =
        UIColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 255/255)
    
    // ランナーリスト選択時
    static let selectYellow: UIColor =
        UIColor(red: 255/255, green: 255/255, blue: 30/255, alpha: 255/255)
}

MyColor.java

public class MyColor {

    // 背景色に使用
    static int lightBlue = android.graphics.Color.argb
            (255,100,240,255);

    // 選択メンバー枠の背景
    static int lightAcuaBlue = android.graphics.Color.argb
            (255,150,255,255);

    // ランナーリストの背景
    static int darkBlue = android.graphics.Color.argb
            (255,50,155,155);

    // ?
    static int lightSkyBlue = android.graphics.Color.argb
            (255,200,245,255);

    // ランナーリスト非選択時
    static int unselectedGrey = android.graphics.Color.argb
            (255,200,200,200);

    // ランナーリスト選択時
    static int selectYellow = android.graphics.Color.argb
            (255,255,255,30);

}

最近、得意げに使ってるstaticです。


これ作っておいて、色指定のところは MyColor.xxxxって書くようにしとけば、色調整の時は、カラーファイルに手をつけるだけで済むんですよね。
クラスをたくさん作るときはこれで決まりですね。

前からそんなんやってたよっていう人もたくさんいるかと思いますが、そういう方は笑ってスルーしてくださいませ。

第36回 java,swift の Enumって

Enum ってなくてもなんとかなりそうだけど、使うとプログラムが見易いですよね。

さっそく、使っている場面を。

swift版  LapTimeManager.swift(旧 Entrance.swift)

class LapTimeManager: UIViewController {
    
    override func viewDidLoad() {
        // 設定
        MySettings.setup()
        Team.setUp()
        // メインメニューの呼び出し
        makeMenu(menu: .Main)
    }
    
    private func removeSubView() {
        for view in self.view.subviews {
            view.removeFromSuperview()
        }
    }
    
    private func makeMenu(menu: Menu) {
        switch menu {
        case .Main:
            removeSubView()
            self.view.addSubview(mainViewMake())
        case .Entry:
            removeSubView()
            self.view.addSubview(entryViewMake())
        default:
            break
        }
    }

java版 LapTimeManager.java(旧 Entrance.java)

public class LapTimeManager extends RelativeLayout {


    public LapTimeManager(Context context) {

        super(context);
        Team.setUp(context);
    }

    public void enterMenu() {

        // 端末のサイズを取得 サイズをMySettingsに保存
        MySettings.screenWidth = this.getWidth();
        MySettings.screenHeight = this.getHeight();

        // 設定
        MySettings.setUp;
        Team.setUp(getContext());

        // メインメニューの呼び出し
        makeMenu(Main);

    }

    private void makeMenu(Menu menu) {
        switch (menu) {
            case Main:
                break;
            case Entry:
                break;
            case Run:
                break;
            case Record:
                break;
        }
    }

MenuというEnumを定義しています。
定義自体はあまり変わりません。

swift版

public enum Menu {
    case Main
    case Entry
    case Run
    case Record
    case Help
}

java

public enum Menu {
    Main,
    Entry,
    Run,
    Record,
    Help
}

ただ問題が・・・
前にも書きましたが、教科書の例題というのは、ファイル数を減らして簡単に例示したいようで、たくさんのファイルの扱い方を示していません。
というのは、Enumを別ファイルにしているのですが、
swift版ではMyEnum.swiftに置いていて稼働しています。

java版でも、MyEnum.java に置こうとしているのですが、

Class 'Menu' is public, should be declared in a file named 'Menu.java'

って怒ってるんですよね。

javaって厳格なのかもしれないけど、ちょっと堅苦しい・・・

1.Enum 名を MyEnumにする。
2.file名を 'Menu.java'にする。
3.Enum定義をClass内に置く。

クラスの肥大化は避けたいので、3はない。というより、もともと、このEnumはLapTimeManager.swift(java) のクラス内に定義してあったものを別ファイルにしようとしているので、3を実行するともとに戻ることになってしまうという、お笑いなんです。

2番が妥当でしょうか。

第35回 static でデータクラス

 またまた、2日間くらい悩みまくりました。
 私のアプリの場合、個人を "Runnerクラスのインスタンス"にして、それを束ねて、Arrayとする。この方向は間違っていないと思ってました。一般的なものでは、顧客クラスや社員クラスなどは同じような扱いなんだろうと思うのですが、他のクラスとやりとりが多く、混沌とした状態になって、とてもお見せできるものとは言えませんでした。

 よく見れば、Runnerは全て Teamクラスに属しています。社員オブジェクトが会社クラスに属しているのと同じなのかなと。
 そして、Teamクラスのコメント欄に、このクラスのインスタンスはアプリ開始時に一回だけ作る・・・と書いてある。いや、自分で忘れないように書いたのだけど、これって、Teamクラスのメンバを全て static にすればいいんじゃないかい?って思ったんですね。

こちらがその Team クラス。

import UIKit
class Team {
    // static でアクセスできるように変更
    // ランナーの所属に関する処理

    static var lastId: Int = 0     // メンバーIDの最後
    static var runners: Array<Runner> = []
    
    // ファイル操作
    static private func loadLastId() -> Int {
        var result: Int
        let userDefault = UserDefaults.standard
        if userDefault.object(forKey: "LastID") != nil {
            result = userDefault.integer(forKey: "LastID")
        } else {
            result = 0
        }
        return result
    }

    static func saveLastId() {
        let userDefault = UserDefaults.standard
        userDefault.set(lastId, forKey: "LastID")
    }
    
    // 選手のマネージメント ===============
    static private func loadAllRunner() {
        // 初期化作業、アプリ開始後に一回だけ実行
        // 登録数が 0 の時は、名前の読み込みはしない
        if lastId != 0 {
            var num: Int = 0
            for i in (1...lastId) {
                // 削除されたメンバーも含まれるが登録しない
                let runner = Runner(runnerId: i)
                if runner.isMember() {
                    num += 1
                    runner.setMyNumer(num: num)
                    runners.append(runner)
                }
            }
        }
    }
    
    // 選手の追加
    static func addRunner(name: String) {
        // 新しいrunnerオブジェクトを作り、氏名を保存する
        lastId += 1
        saveLastId()
        let newRunner: Runner = Runner(runnerId: lastId)
        newRunner.saveName(name: name)
        runners.append(newRunner)
    }
    // 選手の削除
    
}


そして、Runnerクラス。

import UIKit

class Runner {
    
    var myNumber: Int = 0
    let myId: Int
    private var myName: String = ""
    private var member: Bool = false
    var entry: Bool = false
    var lap: Int = 0
    private var time: [Int] = []    // タイムは100倍して切り捨て
    
    init(runnerId: Int) {
        // runnerIdを元にファイルから氏名とステータス(削除)をロードする
        myId = runnerId
        loadName(id: runnerId)
    }
    
    // 名前と部員状態のセーブとロード
    private func loadName(id: Int) {
        let userDefault = UserDefaults.standard
        let nameKey = "name\(id)"       // ファイル上の識別子
        if userDefault.object(forKey: nameKey) != nil {
            myName = userDefault.string(forKey: nameKey)!
        }
        let memberKey = "member\(id)"       // ファイル上の識別子
        if userDefault.object(forKey: memberKey) != nil {
            member = userDefault.bool(forKey: memberKey)
        }
    }
    
    public func saveName(name: String) {
        myName = name
        // ID番号を使って名前をUserDefaultsに保存してお
        let userDefault = UserDefaults.standard
        let nameKey = "name\(myId)"
        userDefault.set(myName, forKey: nameKey)
        let memberKey = "member\(myId)"       // ファイル上の識別子
        userDefault.set(member, forKey: memberKey)
    }
    
    public func setMyNumer(num: Int) {
        myNumber = num
    }

    public func getName() -> String {
        return myName
    }
    
    public func isMember() ->Bool {
        return member
    }
    
    // Entryビューの乗せるための部品
    public func makeRunnerEntryView() -> RunnerEntryView {
        
        let view: RunnerEntryView = RunnerEntryView(number: myNumber, name: myName)
        
        // ハンドラーのセット
        view.setEntryOk {
            self.entry = true
        }
        view.setEntryCancel {
            self.entry = false
        }   
        return view
    }
 
    // ラップデータの処理
    func setLapTime(rapTime: Float) {
        // 100倍して整数化
        let time100 = Int(rapTime*100)
        time.append(time100)
        lap += 1    // ラップ数をカウント
    }
    
    // ラップボタン
    func LapButton(frame: CGRect) -> UIButton {
        let lapButton: UIButton = UIButton(frame: frame)
        lapButton.setTitle(myName, for: .normal)   
        return lapButton
    }
}

Runnerクラスは結構ゴチャゴチャしてます。

こうすると、どのクラスからでも、 Team.runners[番号]の形でアクセスできてしまうので、楽なのかなと思っているところなのです。

staticの使い方を知らなかったので、しかも、正式に教わったものでもないので、これが正しい使い方かどうかわかりません。ま、動けば良しとした方が楽できるし、所詮、アマチュアプログラマーなので。

 ちなみに、某swift教科書には「"static"は構造体、または列挙型特有のプロパティとメソッドに指定する修飾子です。」と書いてある。・・・(よくわかりません)

 せめてもの救いは、javaもswiftも static をキーワードにもっているということで、そこで悩まなくていいので嬉しいかな。