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

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

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

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Xcode

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

Swift

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

Q&A

解決済

2回答

1803閲覧

ModelからViewに対して処理の開始/終了を通知し、処理中の旨を示す表示のON/OFFを行いたい

eleele28

総合スコア18

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Xcode

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

Swift

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

0グッド

0クリップ

投稿2021/11/03 04:08

編集2021/11/03 04:12

前提・実現したいこと

SwiftUIにて画面部及び処理部を作成しています。
今回、処理部で行う処理において10秒程度かかる重たい処理があるため、
処理中は「処理中…」という内容のプログレスビューを画面上に表示し、ユーザにその旨を知らせたいです。

現在は、処理クラスのインスタンスをObservedObjectとして画面側に持ち、
処理クラス内でPublished宣言されている処理開始/終了を示すフラグの変化を画面側で検知して、
表示のON/OFFを切り替えようとしています。

発生している問題・エラーメッセージ

処理開始/終了を示すフラグの変化検知について、画面側で検知できているにはできているのですが、
処理クラスの処理が完全に終わった後(下記コードのstartInspect()がreturnした後)に、処理開始/終了を示すフラグ双方の変化検知がほぼ同時に走ってしまい、困っています。

・実現したい動き
1.処理呼び出し
2.処理開始フラグをtoggle
3.画面側で処理開始フラグ変化検知して処理開始時処理(「処理中」表示フラグON)を実施
4.重たい処理を実行
5.処理終了フラグをtoggle
6.画面側で処理終了フラグ変化検知して処理終了時処理(「処理中」表示フラグOFF)を実施

・現在の動き
1.処理呼び出し
2.処理開始フラグをtoggle
3.重たい処理を実行
4.処理終了フラグをtoggle
5.画面側で処理開始フラグ変化検知して処理開始時処理(「処理中」表示フラグON)を実施
6.画面側で処理終了フラグ変化検知して処理終了時処理(「処理中」表示フラグOFF)を実施

上記のような動きになってしまっているため、下記コードのisProgressShowingフラグが一瞬しかtrueにならず、
プログレスビューを表示することができません。
どうしたら実現したい動きを実施できるのでしょうか?

該当のソースコード

↓画面部

SwiftUI

1import SwiftUI 2 3struct ManualInspection_Crossing: View { 4 5 //プログレスビュー表示フラグ 6 @State var isProgressShowing: Bool = false 7 //処理クラス 8 @ObservedObject var predictorIns: InspectorModel_Crossing = InspectorModel_Crossing() 9 //処理結果 10 @State var predictRslt: Int = 0 11 12 var body: some View { 13 ZStack { 14 15 Button(action: { 16 17 //重い処理を非同期で行う 18 DispatchQueue.main.async { 19 //実施 20 self.predictRslt = predictorIns.startInspect(imageName: self.recentImageName + ".JPG") 21 22 } 23 }, label: { 24 Text("開始") 25 }) 26 27 //測定中はその旨表示する 28 if self.isProgressShowing { 29 Color.white 30 .opacity(0.7) 31 .edgesIgnoringSafeArea(.all) 32 .overlay( 33 ProgressView("処理中…") 34 .foregroundColor(.black) 35 ) 36 } 37 } 38 .frame(width: 700) 39 40 //処理開始時処理 41 .onChange(of: self.predictorIns.proccessingStartedFlg, perform: { value in 42 43 self.isProgressShowing = true 44 45 }) 46 //処理終了時処理 47 .onChange(of: self.predictorIns.proccessingFinishedFlg, perform: { value in 48 49 // 処理中画面非表示 50 self.isProgressShowing = false 51 52 //測定結果がOKであれば 53 if self.predictRslt == Constants.RESULT_OK { 54 55 ~~~後処理を実施~~~ 56 } 57 58 }) 59 60 } 61 62} 63

↓処理部

Swift

1import Foundation 2 3class InspectorModel_Crossing: ObservableObject { 4 //処理開始を知らせるフラグ(開始するとフラグが変化する) 5 @Published var proccessingStartedFlg: Bool = false 6 //処理終了を知らせるフラグ(終了するとフラグが変化する) 7 @Published var proccessingFinishedFlg: Bool = false 8 9 //処理の開始 10 func startInspect(imageName: String, uiImageObj: UIImage? = nil) -> Int { 11 12 self.proccessingStartedFlg.toggle() 13 14 ~~~すごく重たい処理~~~ 15 16 self.proccessingFinishedFlg.toggle() 17 18 return Constants.RESULT_OK 19 } 20 21} 22

補足情報(FW/ツールのバージョンなど)

Xcode:Ver.12.3
シミュレータiOS:14.3
Swift:Ver.5.3.2

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

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

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

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

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

guest

回答2

0

次の2つの処理を別のスレッドで実行するように修正すると、View側に proccessingStartedFlg の変更が意図したタイミングで伝わるようになります。

  • フラグ proccessingStartedFlg を立てる処理
  • 重たい処理

以下にコードの修正例を示します。
重たい処理は Dispatch.main.async で非同期に実行するようにします。そして、その重たい処理が終わった後に startInspect メソッドの呼び出し元に結果コード Constants.RESULT_OK を返すために、 completion というパラメータを追加しています。

diff

1class InspectorModel_Crossing: ObservableObject { 2 //処理開始を知らせるフラグ(開始するとフラグが変化する) 3 @Published var proccessingStartedFlg: Bool = false 4 //処理終了を知らせるフラグ(終了するとフラグが変化する) 5 @Published var proccessingFinishedFlg: Bool = false 6 7 //処理の開始 8- func startInspect(imageName: String, uiImageObj: UIImage? = nil) -> Int { 9+ func startInspect(imageName: String, uiImageObj: UIImage? = nil, completion: ((Int) -> Void)? = nil) { 10 11 self.proccessingStartedFlg.toggle() 12 13- ~~~すごく重たい処理~~~ 14- 15- self.proccessingFinishedFlg.toggle() 16- 17- return Constants.RESULT_OK 18+ DispatchQueue.main.async { 19+ ~~~すごく重たい処理~~~ 20+ 21+ self.proccessingFinishedFlg.toggle() 22+ 23+ completion?(Constants.RESULT_OK) 24+ } 25 }

このメソッドの呼び出す側の修正は以下のようになります。
startInspect メソッドの中で重たい処理を非同期実行するようにしたため、こちらでは DispatchQueue.main.async を使わずに直接startInspect メソッドを呼び出すようにしています。
また、 completion のパラメータから得た結果コードを predictRslt プロパティに代入するように変更しています。

diff

1 var body: some View { 2 ZStack { 3 4 Button(action: { 5- //重い処理を非同期で行う 6- DispatchQueue.main.async { 7- //実施 8- self.predictRslt = predictorIns.startInspect(imageName: self.recentImageName + ".JPG") 9- 10+ //実施 11+ predictorIns.startInspect(imageName: self.recentImageName + ".JPG") { result in 12+ self.predictRslt = result 13 } 14 }, label: { 15 Text("開始") 16 })

追記:
ちょっと長くなってしまいますが、質問に掲載されていたコードをなるべくそのまま使う形にて修正案の動作確認していたときのコードを以下にて共有します。(重たい処理は仮に sleep(10) としています。)

swift

1import SwiftUI 2 3struct Constants { 4 static let RESULT_OK: Int = 0 5} 6 7struct ContentView: View { 8 9 //プログレスビュー表示フラグ 10 @State var isProgressShowing: Bool = false 11 //処理クラス 12 @ObservedObject var predictorIns: InspectorModel_Crossing = InspectorModel_Crossing() 13 //処理結果 14 @State var predictRslt: Int = 0 15 16 var recentImageName = "hoge" 17 18 var body: some View { 19 ZStack { 20 21 Button(action: { 22 //実施 23 predictorIns.startInspect(imageName: self.recentImageName + ".JPG") { result in 24 self.predictRslt = result 25 } 26 }, label: { 27 Text("開始") 28 }) 29 30 //測定中はその旨表示する 31 if self.isProgressShowing { 32 Color.white 33 .opacity(0.7) 34 .edgesIgnoringSafeArea(.all) 35 .overlay( 36 ProgressView("処理中…") 37 .foregroundColor(.black) 38 ) 39 } 40 } 41 .frame(width: 700) 42 43 //処理開始時処理 44 .onChange(of: self.predictorIns.proccessingStartedFlg, perform: { value in 45 46 print("proccessingStartedFlg changed (value=(value))") 47 48 49 self.isProgressShowing = true 50 51 }) 52 //処理終了時処理 53 .onChange(of: self.predictorIns.proccessingFinishedFlg, perform: { value in 54 55 print("proccessingFinishedFlg changed (value=(value))") 56 57 // 処理中画面非表示 58 self.isProgressShowing = false 59 60 //測定結果がOKであれば 61 if self.predictRslt == Constants.RESULT_OK { 62 63// ~~~後処理を実施~~~ 64 } 65 66 }) 67 68 } 69 70} 71 72class InspectorModel_Crossing: ObservableObject { 73 //処理開始を知らせるフラグ(開始するとフラグが変化する) 74 @Published var proccessingStartedFlg: Bool = false 75 //処理終了を知らせるフラグ(終了するとフラグが変化する) 76 @Published var proccessingFinishedFlg: Bool = false 77 78 //処理の開始 79 func startInspect(imageName: String, uiImageObj: UIImage? = nil, completion: ((Int) -> Void)? = nil) { 80 81 self.proccessingStartedFlg.toggle() 82 83 DispatchQueue.main.async { 84// ~~~すごく重たい処理~~~ 85 sleep(10) 86 87 self.proccessingFinishedFlg.toggle() 88 89 completion?(Constants.RESULT_OK) 90 } 91 } 92 93}

投稿2021/11/03 08:51

編集2021/11/03 11:30
__k_san__

総合スコア177

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

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

eleele28

2021/11/03 11:16

ご回答ありがとうございます! 例示の通り試してみましたが、やはり、重たい処理終了後に2つまとめてonChangeにてフラグ変化を拾ってしまいます。 例示のコードではproccessingStartedFlgをtoggleするのはView側のコンテキストのままでしたので、proccessingStartedFlgのtoggleする処理も別途DispatchQueue.main.async節で囲ってみたものの、やはり状況変わりません。 ほかにも別コンテキストで実行しなければならない処理があるのでしょうか?
__k_san__

2021/11/03 11:36 編集

ご確認ありがとうございます。 私が手元で動作確認してみたときには重たい処理の開始時にproccessingStartedFlgについてのonChangeが呼ばれて、重たい処理の終了時にproccessingFinishedFlgについてのonChangeが呼ばれており、2つまとめて(ほぼ同時に)呼ばれるということはありませんでした。 (※ Xcode 13.1、iOSシミュレータ(iPhone 13, iOS 15.0)で確認しました。) 回答の末尾にそのときのコードを追記しましたので、そちらも参考にしていただければと思います。
eleele28

2021/11/04 02:20

返答ありがとうございます。 確かに、ご例示のコードをウチの環境でも動かしてみたところ、意図通りに動作いたしました。 本番のコードはこちらが上記で提示したものにいろいろな画面パーツが肉付けされているため、どこが悪さをしているのかのか検証してみたいと思います。
guest

0

自己解決

処理開始のButtonを置く階層によって、うまく動作する場合と動作しない場合があることが分かりました。

まずView内を以下のよう(ご回答者例示のコード)にした場合、意図通りに動きます。

SwiftUI

1ZStack { 2 3 Button(action: { 4 //実施 5 predictorIns.startInspect(imageName: self.recentImageName + ".JPG") { result in 6 self.predictRslt = result 7 } 8 }, label: { 9 Text("開始") 10 }) 11 12 //測定中はその旨表示する 13 if self.isProgressShowing { 14 Color.white 15 .opacity(0.7) 16 .edgesIgnoringSafeArea(.all) 17 .overlay( 18 ProgressView("処理中…") 19 .foregroundColor(.black) 20 ) 21 } 22}

Buttonを置く階層をだんだん深くし、以下のようにTabViewの中に置いたとたん、意図通りに動かなくなりました。

SwiftUI

1ZStack { 2 VStack { 3 TabView(selection: self.$tabSelection, 4 content: { 5 6 VStack { 7 Button(action: { 8 //実施 9 predictorIns.startInspect(imageName: self.recentImageName + ".JPG") { result in 10 self.predictRslt = result 11 } 12 }, label: { 13 Text("開始") 14 }) 15 } 16 .tabItem { 17 VStack(alignment: .center) { 18 19 Text("Tab1") 20 } 21 }) 22 } 23}

根本的な原因は不明のままですが、startInspect()をTabViewの中以外の階層で呼び出せば動くので、以下のように対応しました。
・自画面内で処理開始イベントを通知するためのStateObjectを宣言
・そのStateObjectのonChangeを拾い、startInspect()を呼び出し

SwiftUI

1import SwiftUI 2 3struct Constants { 4 static let RESULT_OK: Int = 0 5} 6 7//この画面内でイベントを飛ばすためのデータクラス 8class InspectStartEvent: ObservableObject { 9 @Published var inspectStartFlg = false 10} 11 12struct ContentView: View { 13 14 //プログレスビュー表示フラグ 15 @State var isProgressShowing: Bool = false 16 //処理クラス 17 @ObservedObject var predictorIns: InspectorModel_Crossing = InspectorModel_Crossing() 18 //画面内でのイベントを受け取るためのオブジェクト 19 @StateObject var inspectStartEvent: InspectStartEvent = InspectStartEvent() 20 //処理結果 21 @State var predictRslt: Int = 0 22 23 var recentImageName = "hoge" 24 25 var body: some View { 26 ZStack { 27 VStack { 28 TabView(selection: self.$tabSelection, 29 content: { 30 31 VStack { 32 Button(action: { 33 //処理開始イベントを自分に投げる 34 self.inspectStartEvent.inspectStartFlg.toggle() 35 }, label: { 36 Text("開始") 37 }) 38 } 39 .tabItem { 40 VStack(alignment: .center) { 41 42 Text("Tab1") 43 } 44 }) 45 } 46 } 47 .frame(width: 700) 48 //処理開始イベント 49 .onChange(of: self.inspectStartEvent.inspectStartFlg, perform: { value in 50 //実施 51 predictorIns.startInspect(imageName: self.recentImageName + ".JPG") { result in 52 self.predictRslt = result 53 } 54 }) 55 56 //処理開始時処理 57 .onChange(of: self.predictorIns.proccessingStartedFlg, perform: { value in 58 59 print("proccessingStartedFlg changed (value=(value))") 60 61 62 self.isProgressShowing = true 63 64 }) 65 //処理終了時処理 66 .onChange(of: self.predictorIns.proccessingFinishedFlg, perform: { value in 67 68 print("proccessingFinishedFlg changed (value=(value))") 69 70 // 処理中画面非表示 71 self.isProgressShowing = false 72 73 //測定結果がOKであれば 74 if self.predictRslt == Constants.RESULT_OK { 75 76// ~~~後処理を実施~~~ 77 } 78 79 }) 80 81 } 82 83} 84 85

投稿2021/11/04 02:53

eleele28

総合スコア18

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問