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

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

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

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

Swift

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

Q&A

解決済

1回答

197閲覧

【SwiftUI】VideoURLが保存されない or 表示されない

KaoruYoshida

総合スコア36

Xcode

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

Swift

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

0グッド

0クリップ

投稿2024/02/14 08:36

編集2024/02/14 08:41

実現したいこと

献立アプリを作っています。
Core Dataを使用してユーザーがデータベースに料理のメニューを追加でき、またデータベースから料理の情報を取り出してリストに表示できるようにしたいです。
保存、表示したい情報としてはname/sort/time/calorie/image/videoがあります。

発生している問題・分からないこと

AddRecipeViewで、videoの選択はできるのですが、再度開いた時に保存されていない、もしくは保存はされているが表示されないという状態です。

該当のソースコード

AddRecipeView.swift

1import SwiftUI 2 3struct AddRecipeView: View { 4 @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode> 5 @Environment(\.managedObjectContext) private var viewContext 6 @EnvironmentObject var dateHolder: DateHolder 7 8 @State var selectedRecipe: Menu? 9 @State var name: String 10 let sorts = ["", "PASTA & RICE", "MEAT & FISH", "SALAD", "SOUP"] 11 @State private var sortNo = 1 12 @State private var time = 0 13 @State private var calorie = "" 14 @State private var selectedImage: UIImage? 15 @State private var isShowingImagePicker = false 16 @State private var selectedVideoURL: URL? 17 @State private var isShowingVideoPicker = false 18 19 init(alreadyMadeMenu: Menu?) { 20 if let menu = alreadyMadeMenu { 21 _selectedRecipe = State(initialValue: menu) 22 _name = State(initialValue: menu.name ?? "") 23 _sortNo = State(initialValue: Int(menu.sortNo?.sortNo ?? 0)) 24 _time = State(initialValue: Int(menu.time)) 25 _calorie = State(initialValue: menu.calorie ?? "") 26 _selectedImage = State(initialValue: menu.selectedImage.flatMap { UIImage(data: $0) }) 27 if let videoURL = menu.selectedVideoURL as? URL { 28 _selectedVideoURL = State(initialValue: videoURL) 29 } else { 30 _selectedVideoURL = State(initialValue: nil) 31 } 32 } else { 33 _name = State(initialValue: "") 34 _sortNo = State(initialValue: 0) 35 _time = State(initialValue: 0) 36 _calorie = State(initialValue: "") 37 _selectedImage = State(initialValue: UIImage()) 38 _selectedVideoURL = State(initialValue: URL(string: "")) 39 } 40 } 41 var body: some View { 42 Form { 43 Section(header: Text("Menu")){ 44 TextField("料理名", text: $name) 45 Picker("種類を選択", selection: $sortNo) { 46 ForEach(0 ..< sorts.count, id: \.self) { num in 47 Text(self.sorts[num]) 48 } 49 } 50 HStack { 51 Text("調理時間") 52 Spacer() 53 Picker("", selection: $time) { 54 ForEach(1 ..< 91, id: \.self) { num in 55 Text("\(num)分") 56 } 57 } 58 } 59 TextField("カロリー", text: $calorie) 60 VStack { 61 HStack { 62 Text("Photo") 63 .font(.title3) 64 Spacer() 65 } 66 Spacer() 67 HStack { 68 if let image = selectedImage { 69 Image(uiImage: image) 70 .resizable() 71 .aspectRatio(contentMode: .fit) 72 .frame(width: 200, height: 200) 73 } else { 74 Text("写真が選択されていません") 75 } 76 Spacer() 77 Button("写真を選択") { 78 // ボタンがタップされたときに画像選択UIを表示する 79 isShowingImagePicker.toggle() 80 } 81 } 82 .sheet(isPresented: $isShowingImagePicker) { 83 // 画像選択UIを表示する 84 ImagePicker(image: $selectedImage) 85 } 86 } 87 VStack { 88 HStack { 89 Text("Video") 90 .font(.title3) 91 Spacer() 92 } 93 Spacer() 94 HStack { 95 if let videoURL = selectedVideoURL { 96 Text("Selected Video: \(videoURL.lastPathComponent)") 97 } else { 98 Text("ビデオが選択されていません") 99 } 100 Spacer() 101 Button("ビデオを選択") { 102 // ボタンがタップされたときに動画選択UIを表示する 103 isShowingVideoPicker.toggle() 104 } 105 } 106 .sheet(isPresented: $isShowingVideoPicker) { 107 // 動画選択UIを表示する 108 DocumentPicker(url: $selectedVideoURL) 109 } 110 } 111 } 112 Section() { 113 Button("Save", action: saveAction) 114 .font(.headline) 115 .frame(maxWidth: .infinity, alignment: .center) 116 } 117 } 118 } 119 func saveAction() { 120 withAnimation { 121 if selectedRecipe == nil { 122 selectedRecipe = Menu(context: viewContext) 123 } 124 selectedRecipe?.created = Date() 125 selectedRecipe?.name = name 126 selectedRecipe?.time = Double(time) 127 selectedRecipe?.calorie = calorie 128 if let selectedImage = selectedImage { 129 if let imageData = selectedImage.jpegData(compressionQuality: 1.0) { 130 selectedRecipe?.selectedImage = imageData 131 } 132 } 133 selectedRecipe?.selectedVideoURL = selectedVideoURL?.absoluteString 134 // Sortエンティティのインスタンスを取得する 135 let sort = Sort(context: viewContext) 136 sort.sortNo = Int64(sortNo) 137 // selectedRecipeのsortNoプロパティにSortエンティティを代入する 138 selectedRecipe?.sortNo = sort 139 140 dateHolder.saveContext(viewContext) 141 self.presentationMode.wrappedValue.dismiss() 142 } 143 } 144} 145 146struct AddRecipeView_Previews: PreviewProvider { 147 static var previews: some View { 148 AddRecipeView(alreadyMadeMenu: Menu()) 149 } 150} 151

DateHolder.swift

1import Foundation 2import CoreData 3 4class DateHolder: ObservableObject { 5 init(_ context: NSManagedObjectContext) { 6 7 } 8 9 func saveContext(_ context: NSManagedObjectContext) { 10 do { 11 try context.save() 12 } catch { 13 let nsError = error as NSError 14 fatalError("Unresolved error \(nsError), \(nsError.userInfo)") 15 } 16 } 17}

DocumentPicker.swift

1import SwiftUI 2import MobileCoreServices 3 4struct DocumentPicker: UIViewControllerRepresentable { 5 @Binding var url: URL? 6 @Environment(\.presentationMode) var presentationMode 7 8 class Coordinator: NSObject, UIDocumentPickerDelegate { 9 let parent: DocumentPicker 10 11 init(parent: DocumentPicker) { 12 self.parent = parent 13 } 14 15 func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { 16 guard let url = urls.first else { return } 17 parent.url = url 18 parent.presentationMode.wrappedValue.dismiss() 19 } 20 21 func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { 22 parent.presentationMode.wrappedValue.dismiss() 23 } 24 } 25 26 func makeCoordinator() -> Coordinator { 27 Coordinator(parent: self) 28 } 29 30 func makeUIViewController(context: Context) -> UIDocumentPickerViewController { 31 let picker = UIDocumentPickerViewController(documentTypes: [String(kUTTypeMovie)], in: .open) 32 picker.delegate = context.coordinator 33 return picker 34 } 35 36 func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {} 37} 38

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

以下のようなデバック出力を追加してみましたが、 selectedRecipe?.selectedVideoURL は正しく設定されているようでした。

AddRecipeView.swift

1print("selectedVideoURL: \(selectedVideoURL)") 2print("selectedRecipe?.selectedVideoURL: \(selectedRecipe?.selectedVideoURL)") 3selectedRecipe?.selectedVideoURL = selectedVideoURL?.absoluteString 4print("After assignment:") 5print("selectedRecipe?.selectedVideoURL: \(selectedRecipe?.selectedVideoURL)")

AddRecipeView.swift

1selectedRecipe?.selectedVideoURL = selectedVideoURL?.absoluteString 2print("selectedVideoURL: \(selectedVideoURL?.absoluteString ?? "nil")") 3print("selectedRecipe?.selectedVideoURL: \(selectedRecipe?.selectedVideoURL ?? "nil")")

補足

Xcode version14.2
回答にあたり必要な情報があれば伝えていただきたいです。
お分かりになる方、回答よろしくお願いします。

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

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

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

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

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

guest

回答1

0

ベストアンサー

実際に動かして確認などできていないのですが、ソースコードを見て気になった点を書きますね。

16行目のselectedVideoURLはURL型ですよね。
133行目で selectedRecipe?.selectedVideoURL = selectedVideoURL?.absoluteString のようにString型を保存しているように見えます。

27行目では if let videoURL = menu.selectedVideoURL as? URL { のようにURL型にキャストしていますね。
Menu型(エンティティ?)のselectedVideoURLはString型だと思いますので、ここは必ずelseに入りそうに思いましたが、いかがでしょうか?

*今回はビデオに関する項目が問題になっていると思いますので、
そのほかの項目(料理名、調理時間など)の項目は削除した状態で
該当のソースコードをご記載いただけますと読みやすいかなと思いました。
*Menuエンティティの定義もご記載いただけますとより問題が把握しやすいかなと思いました。

追記です。

コメントありがとうございます。
原因がわかったようでよかったです。

・attributeでURL型が選択できない

URLはURIの部分集合らしいですので、
AttributeのTypeにURIを指定すると
SwiftのソースコードからはURL?型として使うことができるみたいですね。

・一方108行目ではBinding<URL?>型を代入しなくてはならない

1点目の対処でもう不要になるかもしれませんが、
URL型が使いづらい場合は、
extensionでString型をURL型に変換してくれるような
Computed Properties を作成するのも良いかもしれませんね。

swift

1extension Menu { 2 var selectedVideoURLString: String { 3 get { 4 selectedVideoURL?.absoluteString ?? "" 5 } 6 set { 7 selectedVideoURL = URL(string: newValue) 8 } 9 } 10} 11 12// getの使い方 13Text(selectedRecipe.selectedVideoURLString) 14// setの使い方 15selectedRecipe.selectedVideoURLString = "https://tenki.jp/forecast/3/"

*後から気づきましたが、例が逆かもしれませんね・・
エンティティはString型でComputed PropertiesはURL型
という記述の方が質問に対する回答として適切でしたね・・
(selectedVideoURLはURL型の前提で記述してしまいました・・)

投稿2024/02/14 11:33

編集2024/02/14 14:38
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

KaoruYoshida

2024/02/14 13:02

なるほど!本当にその通りだと思います。(記載の件も参考になります!) Menuというエンティティの中にselectedVideoURLというattributeを作っているのですが、 このattributeで選択できる型にURL型が無いため、仕方なくString型にして変換をしていました。 以下の2点に悩まされているのですが、どのようにコードを書いて解決すれば良いでしょうか? ・attributeでURL型が選択できない ・一方108行目ではBinding<URL?>型を代入しなくてはならない
KaoruYoshida

2024/02/14 23:07

回答ありがとうございます。 試したところ問題が解決しました! ベストアンサーに選ばせていただきました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.47%

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

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

質問する

関連した質問