AVCaptureSession and white screen - ios11

My layout has two views See the pic:
pic 1
View1 is fullscreen, view2 framed in red bounds is a sublayer of view1. Both views are transparent and do nothing yet but camera broadcast. The code is:
import UIKit
import Foundation
import AVFoundation
class CameraView: UIView {
override class var layerClass: AnyClass {
get {
return AVCaptureVideoPreviewLayer.self
}
}
override var layer: AVCaptureVideoPreviewLayer {
get {
return super.layer as! AVCaptureVideoPreviewLayer
}
}
}
class SceneViewController: ARSceneViewController,
AVCaptureMetadataOutputObjectsDelegate {
var testFrameView: UIView?
var cameraView: CameraView!
override func loadView() {
super.loadView()
cameraView = CameraView()
testFrameView = cameraView
}
override func viewDidLoad() {
super.viewDidLoad()
if let testFrameView = testFrameView {
testFrameView.layer.borderColor = UIColor.red.cgColor
testFrameView.layer.borderWidth = 2
testFrameView.layer.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view.addSubview(testFrameView)
view.bringSubview(toFront: testFrameView)
}
}
}
testFrameView is my view2. Now I want my testFrameView to capture something. That's why class CameraView was created. I add input like this:
import UIKit
import Foundation
import AVFoundation
class CameraView: UIView {
override class var layerClass: AnyClass {
get {
return AVCaptureVideoPreviewLayer.self
}
}
override var layer: AVCaptureVideoPreviewLayer {
get {
return super.layer as! AVCaptureVideoPreviewLayer
}
}
}
class SceneViewController: ARSceneViewController,
AVCaptureMetadataOutputObjectsDelegate {
var testFrameView: UIView?
var cameraView: CameraView!
///////////////// NEW /////////////////
let session = AVCaptureSession()
let sessionQueue = DispatchQueue(label: AVCaptureSession.self.description(), attributes: [], target: nil)
///////////////// NEW /////////////////
override func loadView() {
super.loadView()
cameraView = CameraView()
testFrameView = cameraView
}
override func viewDidLoad() {
super.viewDidLoad()
if let testFrameView = testFrameView {
testFrameView.layer.borderColor = UIColor.red.cgColor
testFrameView.layer.borderWidth = 2
testFrameView.layer.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view.addSubview(testFrameView)
view.bringSubview(toFront: testFrameView)
}
}
///////////////// NEW /////////////////
session.beginConfiguration()
let videoDevice = AVCaptureDevice.default(for: .video)
if (videoDevice != nil) {
let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice!)
if (videoDeviceInput != nil) {
if (session.canAddInput(videoDeviceInput!)) {
session.addInput(videoDeviceInput!)
}
}
}
session.commitConfiguration()
cameraView.layer.session = session
///////////////// NEW /////////////////
}
Now view2 (aka testFrameView or CameraView) is ok, but view1 gets blocked with white screen like this:
pic 2
The question is: why view1 gets blocked when I run capture session using view2. How to avoid it?

Related

Problems with switching array elements

I have a class that has a calculated property, which is an array consisting of instances of the structure.
struct Team: Identifiable, Codable, Hashable {
var id = UUID()
var name : String
}
class TeamRow : ObservableObject {
#Published var teamsArray : [Team] = [] {
didSet {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(teamsArray) {
UserDefaults.standard.setValue(encoded, forKey: "Teams")
}
}
}
init() {
if let teams = UserDefaults.standard.data(forKey: "Teams") {
let decoder = JSONDecoder()
if let decoded = try? decoder.decode([Team].self, from: teams) {
self.teamsArray = decoded
return
}
}
}
}
Also, I have a view, with the ability to add elements(teams) to this array using a sheet.
struct PlayersRow: View {
#ObservedObject var teams = TeamRow()
#State private var team = ""
#State private var showTeamAddSheet = false
var body: some View {
Form {
ForEach(teams.teamsArray) { team in
Text(team.name)
.font(.system(size: 20))
.padding(.horizontal, 110)
.padding(.vertical, 10)
}
}
.navigationBarTitle("Teams")
.navigationBarItems(trailing: Button(action: {
self.showTeamAddSheet = true
}) {
Image(systemName: "plus")
.foregroundColor(.black)
.font(.system(size: 30))
})
.sheet(isPresented: $showTeamAddSheet) {
AddPlayerView(teams: self.teams)
}
}
}
This is a sheet view.
struct AddPlayerView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var teams : TeamRow
#State private var team = ""
var body: some View {
NavigationView {
Form {
TextField("Add new team", text: $team)
}
.navigationBarItems(trailing: Button(action: {
let newTeam = Team(name: self.team)
self.teams.teamsArray.append(newTeam)
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Сохранить")
.font(.custom("coolJazz", size: 20))
.foregroundColor(.black)
}))
.navigationBarTitle("Add Team")
}
}
}
And I have a view where I need to output the array elements one by one, using a button, clicked on the button, the view screen displayed 0 element, clicked on the button, displayed first element, etc.
struct GameView: View {
#ObservedObject var teams = TeamRow()
//#State var index = 0
var body: some View {
VStack(spacing: 40) {
//Text(teams.teamsArray[index]) this isn't worked, return an error: Initializer 'init(_:)' requires that 'Team' conform to 'StringProtocol'
Button(action: {
}) {
Text("press it")
}
}
}
}
struct GameView_Previews: PreviewProvider {
static var previews: some View {
GameView().environmentObject(TeamRow())
}
}
if the array is set initially in the class, I have no problem displaying the elements on the screen by increasing the index, but how to solve this problem I do not know...
Can some one explain newbie?
Change your GameView Code to following:
struct GameView: View {
#ObservedObject var teams = TeamRow()
#State var index = 0
var body: some View {
VStack(spacing: 40) {
if teams.teamsArray.count > index {
Text(teams.teamsArray[index].name)
}
Button(action: {
index += 1
}) {
Text("press it")
}
}
}
}

Swiftui append to a struct array doesn't work

Im learning swiftui at this moment. But now i have come across a problem.
Im trying to append data to an array that is an struct.
struct outfit:Identifiable {
var id = UUID()
var user: String
var amount: Double
var rating: Double
}
and the other file
import SwiftUI
import Firebase
import FirebaseStorage
import FirebaseFirestore
struct rate: View {
private var db = Firestore.firestore()
#State var user = Auth.auth().currentUser
#State private var outfitcards = [outfit]()
#State private var cards = [1, 2, 3]
#State private var offset = [CGSize.zero, CGSize.zero]
init () {
loadcards()
}
var body: some View {
GeometryReader { frame in
ZStack{
VStack {
Text("outfit")
.font(.largeTitle)
.padding()
ZStack {
Text("No cards to show")
.frame(width: frame.size.width * 0.6, height: frame.size.width * 1.6)
HStack {
Image(systemName: "record.circle.fill")
.foregroundColor(.red)
Spacer()
Image(systemName: "record.circle.fill")
.foregroundColor(.green)
}
ForEach(outfitcards) { index in
Text(index.user)
}
}
}
}
}
}
func loadcards () {
db.collection("rating").whereField("user", isNotEqualTo: user?.uid ?? "Error")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err.localizedDescription)")
} else {
for document in querySnapshot!.documents {
let cuser = document.get("user") as! String
let camount = document.get("amount") as! Double
let crating = document.get("rating") as! Double
print("user=\(cuser) amount=\(camount) crating=\(crating)")
outfitcards.append(outfit(user: cuser, amount: camount, rating: crating))
}
print(outfitcards)
}
}
}
}
It does print the username, the amount and the rating but then when i print the array itself it is giving me a []. So it doens't append. Also the for each loop is empty so that also means that the array is empty
and nothing is appended
Does anyone know what I do wrong?
Loading data in a View like that is a dangerous practice and it'll get reset/reloaded any time that view is re-rendered (or even re-inited in this case).
Instead, move your code that loads the data to an ObservableObject:
struct Outfit:Identifiable {
var id = UUID()
var user: String
var amount: Double
var rating: Double
}
class Loader : ObservableObject {
private var db = Firestore.firestore()
private var user = Auth.auth().currentUser
#Published var outfitcards = [Outfit]()
func loadcards () {
db.collection("rating").whereField("user", isNotEqualTo: user?.uid ?? "Error")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err.localizedDescription)")
} else {
for document in querySnapshot!.documents {
let cuser = document.get("user") as! String
let camount = document.get("amount") as! Double
let crating = document.get("rating") as! Double
print("user=\(cuser) amount=\(camount) crating=\(crating)")
self.outfitcards.append(Outfit(user: cuser, amount: camount, rating: crating))
}
print(self.outfitcards)
}
}
}
}
struct Rate: View {
#StateObject var loader = Loader()
#State private var cards = [1, 2, 3]
#State private var offset = [CGSize.zero, CGSize.zero]
var body: some View {
GeometryReader { frame in
ZStack{
VStack {
Text("outfit")
.font(.largeTitle)
.padding()
ZStack {
Text("No cards to show")
.frame(width: frame.size.width * 0.6, height: frame.size.width * 1.6)
HStack {
Image(systemName: "record.circle.fill")
.foregroundColor(.red)
Spacer()
Image(systemName: "record.circle.fill")
.foregroundColor(.green)
}
ForEach(loader.outfitcards) { index in
Text(index.user)
}
}
}
}
}.onAppear {
loader.loadcards()
}
}
}
Now, the loader is responsible for making the database call. It updates a #Published property, which the View observes.
Note that I changed the capitalization of a few things (Rate, Outfit). In Swift, the common practice is to capitalize types (classes/structs/enums) and start variable names with lowercase letters.
Also probably worth noting that the way that you're casting the data from firebase (with as!) is dangerous and can crash your program if the data isn't in the format you expect. Better to use optional binding (let user = document.get("user") as? String)
Your issue is there you are updating #State value in initializing View, which you are doing in Wrong way, do like this onAppear:
struct rate: View {
private var db = Firestore.firestore()
#State var user = Auth.auth().currentUser
#State private var outfitcards = [outfit]()
#State private var cards = [1, 2, 3]
#State private var offset = [CGSize.zero, CGSize.zero]
var body: some View {
GeometryReader { frame in
ZStack{
VStack {
Text("outfit")
.font(.largeTitle)
.padding()
ZStack {
Text("No cards to show")
.frame(width: frame.size.width * 0.6, height: frame.size.width * 1.6)
HStack {
Image(systemName: "record.circle.fill")
.foregroundColor(.red)
Spacer()
Image(systemName: "record.circle.fill")
.foregroundColor(.green)
}
ForEach(outfitcards) { index in
Text(index.user)
}
}
}
}
}
.onAppear() { loadcards() } // <<: Here
}
func loadcards () {
db.collection("rating").whereField("user", isNotEqualTo: user?.uid ?? "Error")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err.localizedDescription)")
} else {
for document in querySnapshot!.documents {
let cuser = document.get("user") as! String
let camount = document.get("amount") as! Double
let crating = document.get("rating") as! Double
print("user=\(cuser) amount=\(camount) crating=\(crating)")
outfitcards.append(outfit(user: cuser, amount: camount, rating: crating))
}
print(outfitcards)
}
}
}
}

SwiftUI: How to build a customizable timer countdown and its separate view

I saw many blogs/articles on how to build a timer with Swift UI. But I cannot figure out to get what I really want working.
The 2 main issues I am facing are:
1. My TimerView is rebuilt whenever its parent view is rebuilt due to states changing
2. I am not able to send parameters to my #ObservedObject TimeCountDown property (2 parameters: duration coming from another view, and an onEnded completion)
class Round: ObservableObject {
#Publishedvar items: [Item]
var duration: Double
init(items: [Item], duration: Double) {
self.items = items
self.duration = duration
}
}
Struct ParentView: View {
#ObservedObject var round: Round
#State private var isRoundTerminated: Bool = false
var body: Some View {
VStack {
if isRoundTerminated {
RoundEndView()
} else {
TimerView(duration: round.duration, onEnded: onTimerTerminated)
RoundPlayView(items: round.items)
}
}
}
}
struct TimerView: View {
#ObservedObject countdown = TimeCountDown()
var duration: Double
var onEnded: (() -> Void)?
///// I DO NOT KNOW HOW TO PROPAGATE THE onEnded completion TO countdown:TimeCountDown
var body: Some View {
Text("There are \(countdown.remainingTime) remaining secs")
.onAppend() {
timer.start(duration: duration)
/// MAYBE I COULD ADD THE onEnded WITHIN THE start() CALL?
}
}
}
class TimeCountDown : ObservableObject {
var timer : Timer!
#Published var remainingTime: Double = 60
var onEnded: (() -> Void)?
init(onEnded: #escaping (() -> Void)?) {
self.onEnded = onEnded
}
func start(duration: Double) {
self.timer?.invalidate()
self.remainingTime = duration
timer = Timer.scheduledTimer(timeInterval:1, target: self, selector:#selector(updateCountdown), userInfo: nil, repeats: true)
}
#objc private func updateCount() {
remainingTime -= 1
if remainingTime <= 0 {
killTimer()
self.onEnded?()
}
}
private func killTimer() {
timer?.invalidate()
timer = nil
}
}
However that does not work...
I also tried to implement the following TimerView:
struct CountdownView: View {
#State private var remainingTime: Int = 60
#Binding var countingDown: Bool
var onEnded: (() -> Void)?
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
init(durations: TimerDuration, onEnded: (() -> Void)?, start: Binding<Bool>) {
self.onEnded = onEnded
self._countingDown = start
self.remainingTime = durations.duration
}
var body: some View {
Text("Remaining \(remainingTime) secs")
.onReceive(timer) {_ in
if self.countingDown {
if self.remainingTime > 0 {
self.remainingTime -= 1
} else {
self.onTerminated()
}
}
}
}
func onTerminated() {
timer.upstream.connect().cancel()
self.remainingTime = 0
onEnded?()
}
}
However when the ParentView is rebuilt very often (due to modifications to round.items (#Published from Round:ObservableObject) the timer can be frozen.
#George_E, please find below my update code (Please note that I simplified the nextWord() func (a but useless here) but it does modify the round.remainingDeck which is a Published property within the ObervableObject Round. That triggers a rebuild of RoundPlayView and that freezes the timer):
import SwiftUI
import Combine
class Round: ObservableObject {
#Published var remainingDeck: [String] = ["Round#1", "Round#2", "Round#3", "Round4"]
var roundDuration: Int = 5
}
struct ContentView: View {
#ObservedObject var round = Round()
var body: Some View {
RoundPlayView(round: round)
}
}
struct RoundPlayView: View {
#ObservedObject var round: Round
var duration: Int {
return round.roundDuration
}
#State private var timerStart: Bool = true
#State private var isTerminated: Bool = false
init(round: Round) {
self.round = round
}
var body: some View {
return VStack {
if self.isTerminated {
EmptyView()
} else {
VStack {
CountdownView(duration: duration, onEnded: timerTerminated, start: $timerStart)
Button(action: { self.addWord() }) {
Text("Add words to the deck")
}
}
}
}
}
private func nextWord() {
round.remainingDeck.append("New Word")
}
private func timerTerminated() {
terminateRound()
}
private func terminateRound() {
self.isTerminated = true
}
}
and here is my TimerView code:
struct CountdownView: View {
#State private var remainingTime: Int = 60
#Binding var countingDown: Bool
var onEnded: (() -> Void)?
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
init(duration: Int, onEnded: (() -> Void)?, start: Binding<Bool>) {
self.onEnded = onEnded
self._countingDown = start
self.remainingTime = duration
}
var body: some View {
Text("Remaining \(remainingTime) secs")
.onReceive(timer) {_ in
if self.countingDown {
if self.remainingTime > 0 {
self.remainingTime -= 1
} else {
self.onTerminated()
}
}
}
}
func onTerminated() {
timer.upstream.connect().cancel()
self.remainingTime = 0
onEnded?()
}
}

ObservableObject bug or not

I still have the problem when the count reaches 3, the reset function only stops it, but the count is not set to 0. I use the reset function with a button, it works perfectly. i would like to understand it and hope someone knows the reason for it?
import SwiftUI
import Combine
import Foundation
class WaitingTimerClass: ObservableObject {
#Published var waitingTimerCount: Int = 0
var waitingTimer = Timer()
func start() {
self.waitingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
self.waitingTimerCount += 1 }}
func stop() { waitingTimer.invalidate() }
func reset() { waitingTimerCount = 0; waitingTimer.invalidate() }
}
struct ContentView: View {
#ObservedObject var observed = WaitingTimerClass()
var body: some View {
VStack {
Text("\(self.observed.waitingTimerCount)")
.onAppear { self.observed.start() }
.onReceive(observed.$waitingTimerCount) { count in
guard count == 3 else {return}
self.observed.reset() // does not work
}
Button(action: {self.observed.start()}) {
Text("Start") }
Button(action: {self.observed.reset()}) { // works
Text("Reset") }
Button(action: {self.observed.stop()}) {
Text("Stop") }
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
It is because reset changes property affecting UI during body construction, so ignored. It should be changed as below
func reset() {
waitingTimer.invalidate()
DispatchQueue.main.async {
self.waitingTimerCount = 0
}
}

Cannot Find Segue Identifier

import UIKit
import SpriteKit
extension SKNode {
class func unarchiveFromFile(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file as String, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as! GameScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
class GameViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
var skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> Int {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return Int(UIInterfaceOrientationMask.AllButUpsideDown.rawValue)
} else {
return Int(UIInterfaceOrientationMask.All.rawValue)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
func move(){
performSegueWithIdentifier("fromGameMoveToMenu", sender: self)
}
}
This is my GameViewController. I hold the reference to this class in a different playscene. I am able to call func move(){}, so there is nothing wrong with actually calling the function. I also have a segue in my main storyboard connecting my GameViewController to my MenuViewController. The segue identifier is named fromGameMoveToMenu. The error says that it cannot find the segue identifier. However, if i put the same performseguewithidentifier function in the viewwillappear, then it will work. Please help!

Resources