Swiftui photoOutput for Images not called every time - swift

This is actually the same issue as this post AVCapturePhotoCaptureDelegate photoOutput() not called every time however no one responded to that . I find that on takePic the photoOutput function is called sometimes and not others it is literally 50/50 . I am using Swiftui 2.0 . Does anyone know a work around this or why this issue is happening ? The code to replicate this is actually quite small .It is the code below and then setting permissions in the info.plist for Privacy - Camera usage description and privacy - photo library usage description . I have tried different things and it is literally still a 50/50 on whether photoOutput gets called . When it is not called you will see this in log print("Nil on SavePic:picData") Any suggestions would be great .
import SwiftUI
import AVFoundation
struct CreateStoryView: View {
#StateObject var camera = CameraModel()
#Environment(\.presentationMode) var presentationMode
var body: some View {
ZStack {
// Going to be Camera Preview
CameraPreview(camera: camera)
.ignoresSafeArea(.all, edges: .all)
VStack {
HStack {
Spacer()
Image(systemName: "arrowshape.turn.up.backward.circle.fill")
.foregroundColor(.black)
.padding(.trailing,20)
.background(Color.white)
.clipShape(/*#START_MENU_TOKEN#*/Circle()/*#END_MENU_TOKEN#*/)
.onTapGesture {
if camera.session.isRunning == true {
camera.session.stopRunning()
}
self.presentationMode.wrappedValue.dismiss()
}
if camera.isTaken {
Button(action: camera.reTake, label: { // camera.reTake
Image(systemName: "arrow.triangle.2.circlepath.camera")
.foregroundColor(.black)
.padding()
.background(Color.white)
.clipShape(/*#START_MENU_TOKEN#*/Circle()/*#END_MENU_TOKEN#*/)
})
.padding(.trailing,10)
}
}
Spacer()
HStack{
// If Taken then showing save and again take button
if camera.isTaken{
Button(action: {if !camera.isSaved{camera.savePic()}}, label: {
Text(camera.isSaved ? "Saved" : "Save")
.foregroundColor(.black)
.fontWeight(.semibold)
.padding(.vertical,10)
.padding(.horizontal,20)
.background(Color.white)
.clipShape(Capsule())
})
.padding(.leading)
Spacer()
} else {
Button(action: camera.takePic , label: {
ZStack{
Circle()
.fill(Color.white)
.frame(width: 65, height: 65)
Circle()
.stroke(Color.white,lineWidth: 2)
.frame(width: 75, height: 75)
}
})
}
}.frame(height: 75)
}
}.onAppear(perform: {
camera.Check()
})
}
}
// Camera Model
class CameraModel: NSObject,ObservableObject,AVCapturePhotoCaptureDelegate {
#Published var isTaken = false
#Published var session = AVCaptureSession()
#Published var alert = false
#Published var output = AVCapturePhotoOutput()
// preview ...
#Published var preview: AVCaptureVideoPreviewLayer!
#Published var isSaved = false
#Published var picData = Data(count: 0)
func Check() {
// first checking camera has permission
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
setUp()
return
case .notDetermined:
//retrusting for permission
AVCaptureDevice.requestAccess(for: .video) {(status) in
if status{
self.setUp()
}
}
case .denied:
self.alert.toggle()
return
default:
return
}
}
func setUp() {
// setting up camera
do{
// setting configs...
self.session.beginConfiguration()
// change for your own
let device = AVCaptureDevice.default(.builtInDualCamera,for: .video,position: .back)
let input = try AVCaptureDeviceInput(device: device!)
// checking and adding session
if self.session.canAddInput(input) {
self.session.addInput(input)
}
// same for output
if (self.session.canAddOutput(self.output)) {
self.session.addOutput(self.output)
}
self.session.commitConfiguration()
} catch {
print(error.localizedDescription)
}
}
// take and retake
func takePic(){
DispatchQueue.global(qos: .background).async {
self.output.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
self.session.stopRunning()
DispatchQueue.main.async {
withAnimation{ self.isTaken.toggle() }
}
}
}
func reTake() {
DispatchQueue.global(qos: .background).async {
self.session.startRunning()
DispatchQueue.main.async {
// withAnimation{ self.isTaken.toggle() }
// clearing
//self.isSaved = false
self.isTaken = false
}
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
print("photoOutput check")
if error != nil {
return
}
guard var imageData = photo.fileDataRepresentation() else {return}
self.picData = imageData
if isSaved == true {
if !imageData.isEmpty {
imageData.removeAll()
isSaved = false
}
} else {
isSaved = true
}
}
func savePic() {
if UIImage(data: self.picData) == nil {
print("Nil on SavePic:picData")
return
}
let image = UIImage(data: self.picData)!
// saving image
UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveError), nil)
self.isSaved = true
print("saved sucessfully")
}
#objc func saveError(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
print("Save finished!")
}
}
// setting up view for preview
struct CameraPreview: UIViewRepresentable {
#ObservedObject var camera: CameraModel
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: UIScreen.main.bounds)
camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
camera.preview.frame = view.frame
camera.preview.videoGravity = .resizeAspectFill
view.layer.addSublayer(camera.preview)
camera.session.startRunning()
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
struct CreateStoryView_Previews: PreviewProvider {
static var previews: some View {
CreateStoryView()
}
}

Related

SwiftUI: Error validating an if based on whether or not there is an instance

I am making a music player in swiftui, there is a part of the code where an instance of the player (audioManager.player) on which several components depend, in order not to set the same value constantly I wrapped the playback part in a conditional (if let player = audioManager.player) based on whether or not there is a “player” instance, but when doing so the content disappears strangely, as if the “player” instance does not exist or is negative, I tried it just validating a part and I realized that everything works correctly but I don't know why the content is not displayed on the canvas or in the simulator. I would appreciate if you help me with a suggestion or solution, everything is welcome. ;)
import Foundation
import AVKit
final class AudioManager: ObservableObject {
var player: AVAudioPlayer?
func startPlayer(track: String, isPreview: Bool = false) {
guard let url = Bundle.main.url(forResource: track, withExtension: "mp3") else {
print("Resourse not found")
return
}
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url)
if isPreview {
player?.prepareToPlay()
} else {
player?.play()
}
} catch {
print("Fail to initialize", error)
}
}
}
import SwiftUI
struct PlayerView: View {
#EnvironmentObject var audioManager: AudioManager
#State private var value: Double = 0.0
#State private var isEditing: Bool = false
var isPreview: Bool = false
let timer = Timer
.publish(every: 0.5, on: .main, in: .common)
.autoconnect()
var body: some View {
ZStack {
VStack(alignment: .center, spacing: 50) {
VStack(spacing: 10) {
// MARK: ** THE ERROR IS HERE **
if let player = audioManager.player {
Slider(value: $value, in: 0...player.duration)
.foregroundColor(.white)
.padding(.top)
}
} // VStack
.padding(.horizontal, 30)
} // VStack
.foregroundColor(.white)
}
.onAppear {
audioManager.startPlayer(track: "meditation1", isPreview: isPreview)
}
.onReceive(timer) { _ in
guard let player = audioManager.player, !isEditing else { return }
value = player.currentTime
}
}
}
struct PlayerView_Previews: PreviewProvider {
static var previews: some View {
Group {
PlayerView(isPreview: true)
.environmentObject(AudioManager())
}
}
}

Array Indexing occurs Fatal error: Index out of range

Problem
Hello. I'm studying SwiftUI.
I've tried to pick multiple photos from gallery using PHPickerController, and show up multiple views which represents each photo one by one. However, Fatal error occurs whenever I try to access any index of vm.images.
How could I solve this issue?
My source code is as follows
Solved
The problem comes from vm.images I thought that .onChange modifier operate after all images are saved into vm.images. But it didn't.
I solved this matter by adding if statement when calling PickerTabView; Quite Easy
// Added code
if let images = vm.images {
if images.count > 0 {
PickerTabView()
}
}
struct PickerTabView: View {
#EnvironmentObject var vm: ViewModel
var body: some View {
TabView {
if let images = vm.images{
ForEach(images, id: \.self) { image in
PickerSettingView(image: image)
}
}
}
.tabViewStyle(.page)
}
}
struct ImagesPicker: UIViewControllerRepresentable {
#Binding var selectedImages: [UIImage]?
//var selectionLimit: Int
//var filter: PHPickerFilter?
var itemProvider: [NSItemProvider] = []
func makeUIViewController(context: Context) -> some PHPickerViewController {
var configuration = PHPickerConfiguration()
configuration.selectionLimit = 20
configuration.filter = .images
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
func makeCoordinator() -> Coordinator {
return ImagesPicker.Coordinator(parent: self)
}
class Coordinator: NSObject, PHPickerViewControllerDelegate, UINavigationControllerDelegate {
var parent: ImagesPicker
init(parent: ImagesPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
if !results.isEmpty {
parent.itemProvider = []
parent.selectedImages = []
}
parent.itemProvider = results.map(\.itemProvider)
loadImage()
}
private func loadImage() {
for itemProvider in parent.itemProvider {
if itemProvider.canLoadObject(ofClass: UIImage.self) {
itemProvider.loadObject(ofClass: UIImage.self) { image, error in
DispatchQueue.main.sync {
if let image = image as? UIImage {
self.parent.selectedImages?.append(image)
}
}
}
}
}
}
}
}
struct PickerHomeView: View {
#EnvironmentObject var vm: ViewModel
#State private var isSelected = false
var body: some View {
NavigationView {
VStack {
NavigationLink("Tab View", isActive: $isSelected) {
PickerTabView()
}
.hidden()
HStack {
Button {
vm.showPicker()
} label: {
ButtonLabel(symbolName: "photo.fill", label: "Photos")
}
}
Spacer()
}
.sheet(isPresented: $vm.showPicker) {
ImagesPicker(selectedImages: $vm.images)
.ignoresSafeArea()
}
.onChange(of: vm.images, perform: { _ in
isSelected = true
})
}
}
}
struct PickerSettingView: View {
#EnvironmentObject var vm: ViewModel
var image: UIImage
let myImage = MyImage(category: Category.unCategorized)
#State private var selectedCategory: Category = Category.unCategorized
var body: some View {
VStack {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(minWidth: 0, maxWidth:.infinity)
SwiftUI.Picker("Category Picker", selection: $selectedCategory) {
Text("Formal").tag(Category.formal)
Text("Casual").tag(Category.casual)
Text("Semi Formal").tag(Category.semiFormal)
}
.pickerStyle(.segmented)
.padding([.leading, .trailing], 16)
HStack {
Button {
if vm.selectedImage == nil {
vm.addMyImage(category: selectedCategory, image: image)
} else {
vm.updateSelected()
}
} label: {
ButtonLabel(symbolName: vm.selectedImage == nil ? "square.and.arrow.down.fill" :
"square.and.arrow.up.fill",
label: vm.selectedImage == nil ? "Save" : "Update")
}
}
}
}
}
class ViewModel: ObservableObject {
#Published var images: [UIImage]?
#Published var showPicker = false
}
I think the image in your loadImage, is not being added to the self.parent.selectedImages
when it is nil, that is, when images in your ViewModel is nil, as it is at the start. So whenever you try to access any index of the images array in your vm.images, the app crashes.
You could try this in your loadImage (note also .async) to append the images:
DispatchQueue.main.async {
if let image = image as? UIImage {
if self.parent.selectedImages == nil { self.parent.selectedImages = [] }
self.parent.selectedImages!.append(image)
}
}

How to search a custom text from textfield in webkit swiftUI

I am building a small browser in swift ui i have build a struct to represent the wkwebview and i want to enter any text in a textfield and search on the internet using wkwebview and a google query i tried to reinstantiate the Webview(web: nil, req: URLRequest(url: URL(string: searchText)!)) but its not working for me? how can i achieve this i am pretty new to SwiftUI.Please help?
struct Webview : UIViewRepresentable {
let request: URLRequest
var webview: WKWebView?
init(web: WKWebView?, req: URLRequest) {
self.webview = WKWebView()
self.request = req
}
func makeUIView(context: Context) -> WKWebView {
return webview!
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
func goBack(){
webview?.goBack()
}
func goForward(){
webview?.goForward()
}
func refresh(){
webview?.reload()
}
}
import SwiftUI
struct ContentView: View {
var webview = Webview(web: nil, req: URLRequest(url: URL(string: "https://www.apple.com")!))
#State private var searchText = ""
#State private var txt = ""
var body: some View {
ZStack {
HStack {
TextField("Search", text: $searchText,onCommit: {
print(searchText)
}
)
.keyboardType(.URL)
.frame(width: UIScreen.main.bounds.size.width * 0.75 )
}
}
webview
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button(action: {
webview.goBack()
}) {
Image(systemName: "arrow.left")
}
Spacer()
Button(action: {
webview.goForward()
}) {
Image(systemName: "arrow.right")
}
Spacer()
Button(action: {
webview.refresh()
}) {
Image(systemName: "arrow.clockwise")
}
}
}
}
}
The way that you have things set up right now (attempting to store a reference to a UIViewRepresentable) is going to lead to problems later on (like this, in fact).
I suggest you set up an interim object of some sort that can be used to store data and communicate imperatively to the WKWebView while still retaining the ability to lay things out declaratively. In my example, WebVewManager is that interim object that both the parent view and the UIViewRepresentable have access to:
class WebViewManager : ObservableObject {
var webview: WKWebView = WKWebView()
init() {
webview.load(URLRequest(url: URL(string: "https://apple.com")!))
}
func searchFor(searchText: String) {
if let searchTextNormalized = searchText.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let url = URL(string: "https://google.com/search?q=\(searchTextNormalized)") { self.loadRequest(request: URLRequest(url: url))
}
}
func loadRequest(request: URLRequest) {
webview.load(request)
}
func goBack(){
webview.goBack()
}
func goForward(){
webview.goForward()
}
func refresh(){
webview.reload()
}
}
struct Webview : UIViewRepresentable {
var manager : WebViewManager
init(manager: WebViewManager) {
self.manager = manager
}
func makeUIView(context: Context) -> WKWebView {
return manager.webview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
struct ContentView: View {
#StateObject private var manager = WebViewManager()
#State private var searchText = ""
#State private var txt = ""
var body: some View {
ZStack {
HStack {
TextField("Search", text: $searchText,onCommit: {
print(searchText)
manager.searchFor(searchText: searchText)
})
.keyboardType(.URL)
.frame(width: UIScreen.main.bounds.size.width * 0.75 )
}
}
Webview(manager: manager)
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button(action: {
manager.goBack()
}) {
Image(systemName: "arrow.left")
}
Spacer()
Button(action: {
manager.goForward()
}) {
Image(systemName: "arrow.right")
}
Spacer()
Button(action: {
manager.refresh()
}) {
Image(systemName: "arrow.clockwise")
}
}
}
}
}
I'm doing some simple percent encoding on the search string -- you may need to do more testing to make sure that google always accepts the search query, but this looks like it's working to me.

On SwiftUI AppKit project, dropEntered of DropDelegate not trigged

The code from https://github.com/onmyway133/blog/issues/594, and make some changes.
It's very simple, create a SwiftUI + Appkit project, copy and paste the code below to the project.
import SwiftUI
struct SelectFileView: View {
let buttonTitle: String
#State var isDrop: Bool = false
var body: some View {
VStack(alignment: .leading) {
Button(action: {}) {
Text(buttonTitle)
}
.offset(x: -16)
Text("Alternatively, you can drag and drop file here")
.font(.footnote)
.foregroundColor(Color.gray)
}
.border(isDrop ? Color.orange : Color.clear)
.onDrop(of: ["public.image"], delegate: self)
.padding(32)
}
}
extension SelectFileView: DropDelegate {
func dropEntered(info: DropInfo) {
print("dropEntered")
self.isDrop = true
}
func dropExited(info: DropInfo) {
print("dropExited")
self.isDrop = false
}
func performDrop(info: DropInfo) -> Bool {
guard
let itemProvider = info.itemProviders(for: ["public.image"]).first
else { return false }
itemProvider.loadItem(forTypeIdentifier: "public.image", options: nil) { item, error in
guard
let data = item as? Data,
let url = URL(dataRepresentation: data, relativeTo: nil)
else { return }
}
return true
}
}
struct ContentView: View {
var body: some View {
SelectFileView(buttonTitle: "Drop Test")
}
}
After building and running on Xcode 12.0 beta 2 (12A6163b), it always print dropExited, and this is the problem.
dropEntered(info:) not trigged, but dropExited(info:) works

Getting nil when calling a wrapped UIViewController's function in a SwiftUI class

So below is a simple picture taking ViewController that I have
final class TakePhotoViewController : UIViewController, AVCapturePhotoCaptureDelegate {
var captureSession : AVCaptureSession!
var cameraOutput : AVCapturePhotoOutput!
var previewLayer : AVCaptureVideoPreviewLayer!
let device = AVCaptureDevice.default(for: .video)!
override func viewDidLoad(){
print("viewDidLoad")
setupCameraLayouts()
}
private func setupCameraLayouts(){
print("setupCameraLayouts")
captureSession = AVCaptureSession()
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
captureSession.sessionPreset = AVCaptureSession.Preset.hd1920x1080
cameraOutput = AVCapturePhotoOutput()
previewLayer.frame = CGRect(x: view.frame.origin.x, y: view.frame.origin.y+view.frame.height/13, width: view.frame.width, height: view.frame.height/1.2475)
do {
try device.lockForConfiguration()
} catch {
return
}
device.focusMode = .continuousAutoFocus
device.unlockForConfiguration()
}
private func startCamera(){
print("startCamera")
if let input = try? AVCaptureDeviceInput(device: device) {
if captureSession.canAddInput(input) {
captureSession.addInput(input)
if captureSession.canAddOutput(cameraOutput){
captureSession.addOutput(cameraOutput)
view.layer.addSublayer(previewLayer)
captureSession.startRunning()
} else {
print("else in : captureSession.canAddOutput(cameraOutput)")
}
} else {
print("else in : captureSession.canAddInput(input)")
}
} else {
print("else in : input = try? AVCaptureDeviceInput(device: device)")
}
}
func cameraPressed(){
print("cameraPressed")
let settings = AVCapturePhotoSettings()
let previewPixelType = settings.availablePreviewPhotoPixelFormatTypes.first!
let previewFormat = [
kCVPixelBufferPixelFormatTypeKey as String: previewPixelType,
kCVPixelBufferWidthKey as String: 160,
kCVPixelBufferHeightKey as String: 160
]
settings.previewPhotoFormat = previewFormat
cameraOutput.capturePhoto(with: settings, delegate: self)
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?){
print("photoOutput")
captureSession.stopRunning()
print("Got something")
}
}
extension TakePhotoViewController : UIViewControllerRepresentable {
public typealias UIViewControllerType = TakePhotoViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<TakePhotoViewController>) -> TakePhotoViewController {
print("makeUIViewController")
return TakePhotoViewController()
}
func updateUIViewController(_ uiViewController: TakePhotoViewController, context: UIViewControllerRepresentableContext<TakePhotoViewController>) {
print("updateUIViewController")
}
}
As you can see, I have it "wrapped" using UIViewControllerRepresentable so I can use it in SwiftUI View. Unless there's better ways to do this, I found that this was the only way to do it.
Below is the SwiftUI class where i'm calling this.
struct ContentView: View {
let TPVC = TakePhotoViewController()
var body: some View {
VStack {
TPVC.startCamera()
Button(action: {
self.TPVC.cameraPressed()
}) {
Text("Hello World")
.fontWeight(.bold)
.font(.title)
.padding()
.background(Color.purple)
.cornerRadius(40)
.foregroundColor(.white)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 40)
.stroke(Color.purple, lineWidth: 5)
)
}
}
}
}
So I know (from the print statements) that the the TakePhotoVC is being called and the nil error variables (and, really, all variables) aren't nil.
That being said, when the error happens (which is when I click the button), the various variables (captureSession, CaptureOutput) are nil which causes obvious errors. In the ContentView I create a variable for the class instance so I can reference to it whenever but it seems that if you call it again it creates a whole new class reference/instance
Your ContentView is a struct, ie. value, it is recreated on each UI refresh, so your TPVC also recreated.
Moreover UIViewControllerRepresentable expected to be some View to be used in ViewBuilder. So it is mis-concept in your code.
The tutorial Interfacing with UIKit should help you.