質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

2回答

1081閲覧

クラスのプロパティを通信処理で得た値を使って定義する方法

swifty

総合スコア38

Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2019/06/30 13:17

チャートを描くためのChartViewというクラスを定義しています。
その中の変数 lastSevenRecords の中に同じクラス内で定義されたファンクション createData(completion: @escaping ([RecordData]) -> Void) 内の通信で得た結果を渡してからChartViewクラスを読み込んでもらいたいです。

その場合はどのようにすればよいでしょうか。

ChartView

1import Foundation 2import Macaw 3import Firebase 4import FirebaseFirestore 5 6class ChartView: MacawView { 7 8 static var lastSevenRecords: [RecordData] = [] 9 static let maxValue = 20 10 static let maxValueinHeight = 20 11 static let lineWidth: Double = 385 12 13 static let dataDivisor = Double(maxValue/maxValueinHeight) 14 static let adjustedData: [Double] = lastSevenRecords.map({ Double($0.score) / dataDivisor }) 15 static var animations: [Animation] = [] 16 17 required init?(coder aDecoder: NSCoder) { 18 super.init(node: ChartView.createChart(), coder: aDecoder) 19 backgroundColor = .clear 20 21 } 22 23 private static func createChart() -> Group { 24 var items: [Node] = addYAxisItems() + addXAxisItems() 25 items.append(createBars()) 26 27 return Group(contents: items, place: .identity) 28 } 29 30 private static func addYAxisItems() -> [Node] { 31 let maxLines = 10 32 let lineInterval = Int(maxValue/maxLines) 33 let yAxisHeight: Double = 600 34 let lineSpacing: Double = 60 35 36 var newNodes: [Node] = [] 37 38 for i in 1...maxLines { 39 let y = yAxisHeight - (Double(i) * lineSpacing) 40 let valueLine = Line(x1: -5, y1: y, x2: lineWidth, y2: y).stroke(fill: Color.black.with(a: 0.10)) 41 let valueText = Text(text: "(i * lineInterval)", font: Font(name: "Avenir-Light", size: 14), align: .max, baseline: .mid, place: .move(dx: -10, dy: y)) 42 valueText.fill = Color.black 43 44 45 newNodes.append(valueLine) 46 newNodes.append(valueText) 47 } 48 49 let yAxis = Line(x1: 0, y1: 0, x2: 0, y2: yAxisHeight).stroke(fill: Color.black.with(a: 0.25)) 50 newNodes.append(yAxis) 51 52 return newNodes 53 } 54 55 private static func addXAxisItems() -> [Node] { 56 let chartBaseY: Double = 600 57 var newNodes: [Node] = [] 58 59     //ここでエラーが出てしまいます 60 for i in 1...adjustedData.count { 61 let x = (Double(i) * 50) 62 let valueText = Text(text: lastSevenRecords[i - 1].recordDate!, font: Font(name: "Avenir-Light", size: 14), align: .max, baseline: .mid, place: .move(dx: x, dy: chartBaseY + 15)) 63 valueText.fill = Color.black 64 newNodes.append(valueText) 65 } 66 67 let xAxis = Line(x1: 0, y1: chartBaseY, x2: lineWidth, y2: chartBaseY).stroke(fill: Color.black.with(a: 0.25)) 68 newNodes.append(xAxis) 69 70 return newNodes 71 } 72 73 private static func createBars() -> Group { 74 let fill = LinearGradient(degree: 90, from: Color(val: 0xff4704), to: Color(val: 0xff4704).with(a: 0.33)) 75 let items = adjustedData.map { _ in Group() } 76 77 animations = items.enumerated().map { (i: Int, item: Group) in 78 item.contentsVar.animation(delay: Double(i) * 0.1) { t in 79 let height = adjustedData[i] * t * 30 80 let rect = Rect(x: Double(i) * 50 + 25, y: 600 - height, w: 30, h: height) 81 82 return [rect.fill(with:fill)] 83 } 84 } 85 86 return items.group() 87 } 88 89 static func playAnimations() { 90 animations.combine().play() 91 92 } 93 94 95 static func createData(completion: @escaping ([RecordData]) -> Void) { 96 97     //こちらで通信処理を行いDBからデータを取得する 98    //↓こちらのrecordDataArrayが得られて使いたいデータ 99     completion(recordDataArray) 100 101 } 102 } 103}

そのために以下のようにRecordViewControllerのviewDidLoad()でCompletion Handlerで得た値を渡そうと試みました。ですがChartViewクラスはこちらのviewDidLoad()に来る前に既に読み込まれてしまっていて lastSevenRecords の配列が空のまま処理を進めてしまっているので lastSevenRecords を使って定義されたadjustedDataが使われる部分で

Thread 1: Fatal error: Can't form Range with upperBound < lowerBound

とエラーが出てしまいます。ChartViewクラスが読み込まれる段階で通信結果を反映させるにはどのようにすればよいのでしょうか?

RecordViewController

1import UIKit 2 3class RecordViewController: UIViewController { 4 5 @IBOutlet private var chartView: ChartView! 6 7 override func viewDidLoad() { 8 super.viewDidLoad() 9 10 chartView.contentMode = .scaleAspectFit 11 ChartView.createData { recordDataArray in 12 13 //Completion Handlerで得られた値(recordDataArray)をChartView内のlastSevenRecordsへ 14 ChartView.lastSevenRecords = recordDataArray 15 ChartView.playAnimations() 16 17 } 18 } 19}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fuzzball

2019/07/01 00:57

通信処理が終わってからcreateChart()を実行すればいいような。 y_waiwaiさんの回答と意味合いは同じです。 処理順を変えればいい話に見えます。
swifty

2019/07/01 09:16

ご回答ありがとうございます。 おっしゃる通りでそれが実現したいです。 今の状態だと、RecordViewController側の override func viewDidLoad() { super.viewDidLoad() 直後でprint("test") としてもそこまでいかないChartViewの段階で上記のようにエラーが出てしまうのでChartView側の動きの時系列をコントロールできずどうすればいいのだろうかと困ってしまっております。 通信処理が終わってからcreateChart()を実行するには具体的にどのように書けば可能なのでしょうか? 質問ばかりで大変恐縮ですが引き続き何卒よろしくお願いいたします。
fuzzball

2019/07/01 09:37

createChart() の呼び出しタイミング(initの中)を変えられないのであれば、y_waiwaiさんの回答のように生成タイミングを変えるしかなさそうな。
swifty

2019/07/01 12:09

お世話になっております。クラスの生成タイミングを変えたいと思うのですがどのようにしてかえればよいのでしょうか?やはりRecordViewControllerの記述をするということではないのでしょうか?いろいろ試してみておりますが思った挙動を実現できません。
guest

回答2

0

ベストアンサー

通信結果を取得してからそのクラスを生成してはどうでしょう

投稿2019/06/30 15:00

y_waiwai

総合スコア87774

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

swifty

2019/07/01 09:07

ご回答ありがとうございます。teratailでの初めての回答なので涙が出そうになるほど嬉しいです。 「通信結果を取得してからそのクラスを生成する」というのはRecordViewControllerのcreateDataのクロージャー内でChartViewクラスのインスタンスを生成するということでしょうか? そうだとすると私なりに同じことを考えて、RecordViewControllerのcreateDataのクロージャー内でChartView.lastSevenRecordsにrecordDataArrayを渡してみました。でもこういった方法ではないということでしょうか? 普通クラスのインスタンスを生成する際は let chartView = ChartView() としてから chartView.〇〇 でクラス内にアクセスしますが今回はChartView内の変数やファンクションがstaticで定義されているためその過程を踏まずに作られていることが何か鍵になっているのでしょうか? 引き続き何卒よろしくお願いいたします。
guest

0

みなさまご回答ありがとうございました。

只今無事みなさまのご回答を参考にした上で自己解決することができました。

その上でy_waiwaiさんが提案してくださった

「通信結果を取得してからそのクラスを生成してはどうでしょう」

ということについて徹底的に考えた結果答えが出せましたのでベストアンサーにさせていただきました。

ポイントになったのはStoryboardを使わずコードでオリジナルViewのサブクラスを生成し、その後そのViewを編集し直して表示するということでした。

Firestoreからデータを取得してCompletion Handlerを使って渡すという私自身の考え方とやりかたが間違っていたわけではなく、今回の問題はそちらではありませんでした。

とにかくその前段階としてオリジナルのchartView(取得したデータを使ったchartView)を生成する前にStoryboardで作っていたchartViewによりChartを表示したいページに行くと自動でそのChartViewクラスのイニシャライザが作動してしまい、Chart描画のための準備に入ってしまっており、その段階では取得したデータはまだ存在していないのでエラーになってしまっていました。

そのためChartViewクラスの初期化設定を最初にCGRect情報さえいれれば作れる以下のように修正し、データ取得後、得た情報を使って初期化時仮に作っておいたchartViewを書き換えるという作業をRecordViewController側ですることで解決しました。

ChartView

1 override init(frame: CGRect) { 2 super.init(frame: frame) 3 backgroundColor = .clear 4 5 } 6 7 @objc required convenience init?(coder aDecoder: NSCoder) { 8 fatalError("init(coder:) has not been implemented") 9 }

今回の問題でUIViewのサブクラスの生成について深く学ぶことができました。
これで今まで以上にオリジナルのUIViewをコントロールしていくことができそうです。

みなさまのご協力ありがとうございました。

投稿2019/07/02 14:14

swifty

総合スコア38

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問