回答編集履歴

1

質問へのコメントを受けて編集

2020/07/19 06:44

投稿

TsukubaDepot
TsukubaDepot

スコア5086

test CHANGED
@@ -1,3 +1,377 @@
1
+ ちょっと興味があったので試してみました。
2
+
3
+
4
+
5
+ - [AVFoundationを用いたカメラの実装](https://medium.com/@shiba1014/avfoundation%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E3%82%AB%E3%83%A1%E3%83%A9%E3%81%AE%E5%AE%9F%E8%A3%85-a8f462392265)
6
+
7
+
8
+
9
+ 上記の記事をみると、プレビューについては参考にされた Qiita の記事
10
+
11
+
12
+
13
+ - [Swift4で動画を撮影・保存する](https://qiita.com/daigou26/items/74bbdfce46db8898fb47)
14
+
15
+
16
+
17
+ と共通のようです。
18
+
19
+
20
+
21
+ あとは、撮影時に「ビデオにする」か「写真にする」かで処理を変えれば良さそうに思えました。
22
+
23
+
24
+
25
+ ということで、双方の記事を参考に、共通のプレビューで動画撮影と静止画撮影を個別にできるようなコードを書いてみました。
26
+
27
+
28
+
29
+ 今回は個別にボタンを用意していますが、実際は通常のタップろロングタップで処理を切り替えれば良さそうです。
30
+
31
+
32
+
33
+ 本来であれば、ビデオ撮影時は静止画を撮影できない(ボタンを選択できないようにする)などの処理が必要ですが、その辺りは無視していますのでご注意ください。
34
+
35
+
36
+
37
+ また、写真については露出などの設定をしていないため、暗くなってしまうようですが、そのあたりは適切に手を加えていただければと思います。
38
+
39
+
40
+
41
+ あと、Swift5 で書いていますので、Qiita の元記事(Swift4)とは一部メソッドなどが異なっている点にもご注意ください。
42
+
43
+
44
+
45
+ ```Swift
46
+
47
+ // https://medium.com/@shiba1014/avfoundation%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E3%82%AB%E3%83%A1%E3%83%A9%E3%81%AE%E5%AE%9F%E8%A3%85-a8f462392265
48
+
49
+
50
+
51
+ import UIKit
52
+
53
+ import AVFoundation
54
+
55
+ import Photos
56
+
57
+
58
+
59
+ // MARK: AVCapturePhotoCaptureDelegate を追加
60
+
61
+ class RecordViewController: UIViewController, AVCaptureFileOutputRecordingDelegate, AVCapturePhotoCaptureDelegate {
62
+
63
+ let fileOutputForVideo = AVCaptureMovieFileOutput()
64
+
65
+
66
+
67
+ var recordButton: UIButton!
68
+
69
+ //
70
+
71
+ var stillButton: UIButton!
72
+
73
+
74
+
75
+ var isRecording = false
76
+
77
+
78
+
79
+ // MARK: 移動 - トップレベルで宣言
80
+
81
+ let captureSession = AVCaptureSession()
82
+
83
+
84
+
85
+ override func viewDidLoad() {
86
+
87
+ super.viewDidLoad()
88
+
89
+
90
+
91
+ setUpPreview()
92
+
93
+ }
94
+
95
+
96
+
97
+ func setUpPreview() {
98
+
99
+ let videoDevice = AVCaptureDevice.default(for: AVMediaType.video)
100
+
101
+ let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
102
+
103
+
104
+
105
+ do {
106
+
107
+ if videoDevice == nil || audioDevice == nil {
108
+
109
+ throw NSError(domain: "device error", code: -1, userInfo: nil)
110
+
111
+ }
112
+
113
+
114
+
115
+ // MARK: 移動 - ほかの関数から参照するので、トップレベルに移動
116
+
117
+ //let captureSession = AVCaptureSession()
118
+
119
+
120
+
121
+ // video inputを capture sessionに追加
122
+
123
+ let videoInput = try AVCaptureDeviceInput(device: videoDevice!)
124
+
125
+ captureSession.addInput(videoInput)
126
+
127
+
128
+
129
+ // audio inputを capture sessionに追加
130
+
131
+ let audioInput = try AVCaptureDeviceInput(device: audioDevice!)
132
+
133
+ captureSession.addInput(audioInput)
134
+
135
+
136
+
137
+ // MARK: 撮影直前に設定する
138
+
139
+ // max 30sec
140
+
141
+ //fileOutputForVideo.maxRecordedDuration = CMTimeMake(value: 30, timescale: 1)
142
+
143
+ //captureSession.addOutput(fileOutputForVideo)
144
+
145
+
146
+
147
+ // プレビュー
148
+
149
+ let videoLayer : AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
150
+
151
+ videoLayer.frame = self.view.bounds
152
+
153
+ videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
154
+
155
+ self.view.layer.addSublayer(videoLayer)
156
+
157
+
158
+
159
+ captureSession.startRunning()
160
+
161
+
162
+
163
+ setUpButtonForVideo()
164
+
165
+ // MARK: 写真用のボタン
166
+
167
+ setUpButtonForPhoto()
168
+
169
+ } catch {
170
+
171
+ // エラー処理
172
+
173
+ }
174
+
175
+ }
176
+
177
+
178
+
179
+ func setUpButtonForVideo() {
180
+
181
+ recordButton = UIButton(frame: CGRect(x: 0,y: 0,width: 120,height: 50))
182
+
183
+ recordButton.backgroundColor = UIColor.gray
184
+
185
+ recordButton.layer.masksToBounds = true
186
+
187
+ recordButton.setTitle("録画開始", for: .normal)
188
+
189
+ recordButton.layer.cornerRadius = 20.0
190
+
191
+ recordButton.layer.position = CGPoint(x: self.view.bounds.width/2, y:self.view.bounds.height-50)
192
+
193
+ recordButton.addTarget(self, action: #selector(onClickRecordButtonForVideo(sender:)), for: .touchUpInside)
194
+
195
+
196
+
197
+ self.view.addSubview(recordButton)
198
+
199
+ }
200
+
201
+
202
+
203
+ // MARK: 写真撮影用のボタン
204
+
205
+ func setUpButtonForPhoto() {
206
+
207
+ stillButton = UIButton(frame: CGRect(x: 0,y: 0,width: 120,height: 50))
208
+
209
+ stillButton.backgroundColor = UIColor.gray
210
+
211
+ stillButton.layer.masksToBounds = true
212
+
213
+ stillButton.setTitle("撮影", for: .normal)
214
+
215
+ stillButton.layer.cornerRadius = 20.0
216
+
217
+ stillButton.layer.position = CGPoint(x: self.view.bounds.width/2, y:self.view.bounds.height-124)
218
+
219
+ stillButton.addTarget(self, action: #selector(onClickRecordButtonForPhoto(sender:)), for: .touchUpInside)
220
+
221
+
222
+
223
+ self.view.addSubview(stillButton)
224
+
225
+ }
226
+
227
+
228
+
229
+ @objc func onClickRecordButtonForVideo(sender: UIButton) {
230
+
231
+ if !isRecording {
232
+
233
+ // MARK: 移動 - ビデオ撮影前に出力を追加
234
+
235
+ fileOutputForVideo.maxRecordedDuration = CMTimeMake(value: 30, timescale: 1)
236
+
237
+ captureSession.addOutput(fileOutputForVideo)
238
+
239
+
240
+
241
+ // 録画開始
242
+
243
+ let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
244
+
245
+ let documentsDirectory = paths[0] as String
246
+
247
+ let filePath : String? = "(documentsDirectory)/temp.mp4"
248
+
249
+ let fileURL : NSURL = NSURL(fileURLWithPath: filePath!)
250
+
251
+ fileOutputForVideo.startRecording(to: fileURL as URL, recordingDelegate: self)
252
+
253
+
254
+
255
+ isRecording = true
256
+
257
+ changeButtonColor(target: recordButton, color: UIColor.red)
258
+
259
+ recordButton.setTitle("録画中", for: .normal)
260
+
261
+ } else {
262
+
263
+ // 録画終了
264
+
265
+ fileOutputForVideo.stopRecording()
266
+
267
+
268
+
269
+ isRecording = false
270
+
271
+ changeButtonColor(target: recordButton, color: UIColor.gray)
272
+
273
+ recordButton.setTitle("録画開始", for: .normal)
274
+
275
+
276
+
277
+ // MARK: 追加 - 撮影後にビデオ出力を削除
278
+
279
+ // 手持ちの iPhone だと、出力は1つしか追加できないので、使用後は削除する
280
+
281
+ captureSession.removeOutput(fileOutputForVideo)
282
+
283
+ }
284
+
285
+ }
286
+
287
+
288
+
289
+ // MARK: 追加 - 撮影の処理
290
+
291
+ @objc func onClickRecordButtonForPhoto(sender: UIButton) {
292
+
293
+ // 必要であれば露出などのセッティングを行う
294
+
295
+ let settings = AVCapturePhotoSettings()
296
+
297
+ let fileOutputForPhoto = AVCapturePhotoOutput()
298
+
299
+
300
+
301
+ // 撮影前にデバイスを追加し、撮影。終了後はデバイスを削除
302
+
303
+ captureSession.addOutput(fileOutputForPhoto)
304
+
305
+ fileOutputForPhoto.capturePhoto(with: settings, delegate: self)
306
+
307
+ captureSession.removeOutput(fileOutputForPhoto)
308
+
309
+ }
310
+
311
+
312
+
313
+ func changeButtonColor(target: UIButton, color: UIColor) {
314
+
315
+ target.backgroundColor = color
316
+
317
+ }
318
+
319
+
320
+
321
+ func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
322
+
323
+ // ライブラリへ保存
324
+
325
+ PHPhotoLibrary.shared().performChanges({
326
+
327
+ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputFileURL)
328
+
329
+ }) { completed, error in
330
+
331
+ if completed {
332
+
333
+ print("Video is saved!")
334
+
335
+ }
336
+
337
+ }
338
+
339
+ }
340
+
341
+
342
+
343
+ // MARK: 静止画の保存
344
+
345
+ func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
346
+
347
+ guard let photoData = photo.fileDataRepresentation(),
348
+
349
+ let image = UIImage(data: photoData) else { return }
350
+
351
+
352
+
353
+ PHPhotoLibrary.shared().performChanges({
354
+
355
+ PHAssetChangeRequest.creationRequestForAsset(from: image)
356
+
357
+ }) { completed, error in
358
+
359
+ if completed {
360
+
361
+ print("Photo is saved!")
362
+
363
+ }
364
+
365
+ }
366
+
367
+ }
368
+
369
+ }
370
+
371
+ ```
372
+
373
+ # 以前の回答
374
+
1
375
  1つのボタンに対し、通常のタップ時の処理と、ロングタップ時の処理両方をつけ、それぞれの動作に応じて写真撮影、あるいは動画撮影と分ければ可能なように思えます。
2
376
 
3
377