第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 をキーワードにもっているということで、そこで悩まなくていいので嬉しいかな。
第34回 removeFromSuperview()でハマる
氏名入力をするために、スクロールビューのボタンから、氏名を入力する画面になっています。次に名前を登録のボタンを押したら、スクロールビューに名前が入り、全画面に戻るというのが私のシナリオですが・・・
なんということでしょう。
名前を登録ボタンの飛び先のコードにremoveFromSuperview()と書くと、Thread 1:signal SIGABRTというエラーメッセージが出て、怒られてしまいました。コンパイルは通るが実行時に不具合が生じるとよくこのエラーになるようです。しかも、このエラーの対処がなかなか見つからない・・・
なんども言います。素人なんです、私。エラーログ読んだりとかしないので。
そこで、原因を考えてみると、オブジェクトとして、NameInputViewがあり、そこにボタンの記述やらボタン処理やら書いています。そのボタン処理の中で、NameInputViewのインスタンスを、removeFromSuperview()・・・
と、ここで、処理中のプログラムが消えてしまうってのが、原因らしい。
走っている電車の前にあるレールを取っ払うみたいなことをしてた。これはまずいです。
そこで、急遽、Teamクラスにコードを置き、Teamクラスの中から、消すことにしました。
こんな感じです。
public func makeNameInputView() { let view: UIView = UIView(frame: MySettings.getFullFrame()) view.backgroundColor = UIColor(red: 0, green: 155/255, blue: 155/255, alpha: 220/255) view.tag = MySettings.Entry.NameInputArea self.view.addSubview(view) // 走者名を登録します let infomation: UILabel = UILabel(frame:MySettings.Entry.imfomationFrame()) infomation.text = "走者名を登録します" infomation.textAlignment = .center infomation.backgroundColor = UIColor.white view.addSubview(infomation) //入力用textField let nameField: UITextField = UITextField(frame: MySettings.Entry.nameFieldFrame()) nameField.backgroundColor = UIColor.white view.addSubview(nameField) nameField.resignFirstResponder() //OKボタン let okButton: UIButton = UIButton(frame: MySettings.Entry.okButtonFrame()) okButton.setTitle("名前を登録", for: .normal) okButton.setTitleColor(UIColor.black, for: .normal) okButton.backgroundColor = UIColor.lightGray okButton.tag = MySettings.Entry.OkButtonTag okButton.addTarget(self, action: #selector(onClick), for: UIControlEvents.touchUpInside) view.addSubview(okButton) //キャンセルボタン let cancelButton: UIButton = UIButton(frame: MySettings.Entry.cancelButtonFrame()) cancelButton.setTitle("キャンセル", for: .normal) cancelButton.setTitleColor(UIColor.black, for: .normal) cancelButton.backgroundColor = UIColor.lightGray cancelButton.tag = MySettings.Entry.CanselButtonTag cancelButton.addTarget(self, action: #selector(onClick), for: UIControlEvents.touchUpInside) view.addSubview(cancelButton) nameField.becomeFirstResponder() } // ボタンの動作 @objc func onClick(sender: UIButton) { if sender.tag == MySettings.Entry.OkButtonTag { // OKボタンの時だけ、新規のメンバーを作成 } let view = self.view.viewWithTag(MySettings.Entry.NameInputArea) view?.removeFromSuperview()
途中のaddTargetの書式、action: #selector(onClick)っていうところ、昔の本と違いますよね。なんか面倒になってます。
ま、これで、無事消えることとなりました。
第33回 氏名入力は、UITextFieldを使って入れればいいんだね。
ゲームでは、あんまり使わない、文字入力なんですが、これはもう本当にプロの人から見れば一笑に付されるようなことしていると思います。
とりあえず、iPhoneでこんな画面を作りました。
左側のウインドウはスクロールビューで、氏名が入るとことなんですが、最後に氏名登録ボタンを設置しました。これをタップすることで、氏名入力画面が出てくるようにしています。
ところで、オブジェクトの階層でいうと、EntryView(全体) の下にスクロールビューや出場者の表示、その他のボタンがあり、スクロールビューの中に、登録された選手名(今は0人)とこの登録ボタンがあります。
で、この登録ボタンがトリガーになって、右側の状態になるというのが今回考えた部分ですが、右側の画面は、一つのクラスになっていて、独立したものです。
import UIKit class NameInputView: UIView,UITextFieldDelegate { override init(frame: CGRect) { super.init(frame: frame) // 背景色は半透明の青色 backgroundColor = UIColor(red: 0, green: 155/255, blue: 155/255, alpha: 220/255) // 走者名を登録します let infomation: UILabel = UILabel(frame:MySettings.Entry.imfomationFrame()) infomation.text = "登録する氏名を入力してください" infomation.textAlignment = .center infomation.backgroundColor = UIColor.white addSubview(infomation) //入力用textField let nameField: UITextField = UITextField(frame: MySettings.Entry.nameFieldFrame()) nameField.backgroundColor = UIColor.white addSubview(nameField) nameField.resignFirstResponder() //OKボタン let okButton: UIButton = UIButton(frame: MySettings.Entry.okButtonFrame()) okButton.setTitle("名前を登録", for: .normal) okButton.setTitleColor(UIColor.black, for: .normal) okButton.backgroundColor = UIColor.lightGray addSubview(okButton) //キャンセルボタン let cancelButton: UIButton = UIButton(frame: MySettings.Entry.cancelButtonFrame()) cancelButton.setTitle("キャンセル", for: .normal) cancelButton.setTitleColor(UIColor.black, for: .normal) addSubview(cancelButton) nameField.becomeFirstResponder() // キーボードの表示 } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
まあ、どこかの教科書に載ってるような、だらっとしたプログラムですが、素人がすることだから、こんなものでしょう。
最後の .becomeFirstResponder() はキーボードを表示するもので、入力モードにしてしまいたいので入れてあります。
この後は、名前を登録ボタンを押した時、Runnerクラスに実装する手順になります。
私のこの連載を続けて読んでいただいている方はお分かりだと思いますが、Android,iOSの両方で、同じ構造、同じ動作をするプログラムを作るのが目的です。この部分はまだAndroid版に取り掛かっていませんが、今回のiOSでのUILabel、UITextField、UIButtonはAndroidにも同じ趣旨のものがあるようなので、基本的な部品を使って、(手抜きして)作って見ました。
第32回 凡ミス。setPuddingとsetMerginsについて
第20回で、setMarginsが使えない。みたいなことを書きましたが、凡ミスでした。
setPadding も setMargins もちゃんとあるのですが、どこか見つけられなかっただけと言うお粗末な結論です。
しかし、これは、きちんと書き留めなければいけないと言うことで、書きます。
setPaddingの方はViewクラスに定義されたメソットということですね。
なので、
View view = new View(getContext()); view.setPadding(10,10,10,10);
とやればいいんです。
そして、setMarginsを同じように探したために、失敗してしまいました。
setMarginsはレイアウトパラメータにセットするものだったんですね。
私、RelativeLayoutしか使わないので。
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT ); params.setMargins(10,10,10,10);
とにかく、解決してよかった。これで、レイアウトが自由にかけるようになります。
第31回 JAVA の ArrayLiist は便利!
アプリの画面が少しだけ前に進んでいます。
懸案だった横に長い氏名表示域を短くし、右側にエントリーされた選手名を表示するようにしました。
注目すべきは、タップした順に出場者が表示されている事で、なんとこれは自然になってしまったというjava の仕様なんですね。
最初考えたときはそうなればいいなと思ったけど、面倒なプログラムが必要だろうな、やめようかなと思っていたのですが、ArrayListを使うと後ろに追加、要素を探して削除、というのがとても簡単にできるのです。
まず、次のようなstatic なデータ置き場を作っていきます。
import java.util.ArrayList; public class EntryNames { static ArrayList<String> entryMember = new ArrayList<>(); public static ArrayList<String> getEntryMember() { return entryMember; } public static void addEntryName(String name) { entryMember.add(name); } public static void removeName(String name) { entryMember.remove(name); } public static int count() { return entryMember.size(); } }
最初の static ArrayList
これは、氏名(文字列)の入れ物となります。
氏名を入れるのは メソッドの entryMember.add(name); で引数の名前を追加してくれます。
そして、entryMember.remove(name); でその名前と同じ要素を削除してくれます。
なんと素晴らしい事。こんな簡単に実現するのだ。驚き。
そして、これは static なものだから、別のクラスから簡単にアクセスできてしまう。
結論から言うと、データの共有化は static に限る!
ちょっと
言い過ぎですかね。
でも、実際、左側の名簿から、右側のリストにどうやって名前を移すか、すごい長い時間、考えていました。
一歩前進です。
次は swift でやってみよう。
第30回 static を使いまくる
何をしてるのか・・・
しかし、かっこいいから、時間を忘れてプログラムを改造中。
元のプログラム。
if (entry) { setBackgroundColor(Color.argb(255,255,255,30)); } else { setBackgroundColor(Color.argb(255,200,200,200)); }
こうなりました。
if (entry) { setBackgroundColor(MySettings.Color.selectYellow); } else { setBackgroundColor(MySettings.Color.unselectGrey); }
そして、MySettings.javaには、こんな風に書いてます。
// アプリ専用のカラー定義 static class Color { static int lightBlue = android.graphics.Color.argb(255,100,240,255); static int unselectGrey = android.graphics.Color.argb(255,200,200,200); static int selectYellow = android.graphics.Color.argb(255,255,255,30); }
他のところで、色を使う時、流用しやすいのではと思っています。
最初に気づけよ〜〜って言っても遅いんだから。
第29回 Static が便利だった!
今まであんまり使ったことのない static キーワード。何かカウントするときとか便利だよ〜とかって、それくらいの知識だけで。
でも、これって、インスタンス作らんでも使えるやんってことで、ビューを作る情報が散乱してるのを集めてみることにしました。
import UIKit class MySettings { // このクラスにはいろいろなビューのサイズを保管する static var width: CGFloat = 0 static var height: CGFloat = 0 static var sizeRetio: CGFloat = 0.24 // 縦横の比 横を100に固定 // エントリー用のCGSizeを返す static func runnerEntryViewSize() -> CGSize { let w = width * 0.4 let h = w * sizeRetio return CGSize(width: w, height: h) } static func runnerErentryViewWidth() -> CGFloat { return runnerEntryViewSize().width } static func runnerErentryViewHeight() -> CGFloat { return runnerEntryViewSize().height } // エントリービューのフレーム、とりあえず、全画面 static func entryViewFrame() -> CGRect { return CGRect(x: 0, y: 0, width: width, height: height) } // エントリー用のCGRectを返す.スクロールビューでの座標 static func runnerEntryViewFrame(num: Int) -> CGRect { // 引数num によって縦位置が変わる。 let o: CGPoint = CGPoint(x: 0, y: runnerErentryViewHeight() * CGFloat(num-1)) return CGRect(origin: o, size: runnerEntryViewSize()) } // エントリー用スクロールビューのframe これは見える部分 static func entryScrollViewFrame() -> CGRect{ let frame: CGRect = CGRect(x: width*0.5, y: width*0.15, width: width*0.4, height: height - width * 0.4) return frame } // エントリー用スクロールビューのContentSize 中身のサイズ static func entryViewContentSize(num: Int) -> CGSize { let size: CGSize = CGSize(width: runnerErentryViewWidth(), height: runnerErentryViewHeight() * CGFloat(num)) return size } }
名前が長い目ですが、後で利用することを考えるとわかりやすい方がいいのかなと。
で、使う方は、こんな感じです。
// 自前のイニシャライザ init(number: Int, name: String) { myNumber = number myName = name super.init(frame: MySettings.runnerEntryViewFrame(num: number)) setBackGroundColor() } private func setBackGroundColor() { // 背景色をセット、エントリー時には色を変える 色は未調整 if entry { backgroundColor = UIColor(red: 255/255, green: 255/255, blue: 30/255, alpha: 255/255) } else { backgroundColor = UIColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 255/255) } }
デザインを変えたい時には、MySettings.swiftだけいじれば良いのでとても楽になりました。でも、これって、プログラムの世界じゃ常識なのかもしれない・・・(未熟だったな〜)
今回は、static変数、static関数を用いて、プログラムを見やすくしようということでした。
MySettingsの中身をいじるだけで、ビューの位置を自由に変えられるようになりました。