How to loop multiple AVPlayers in Swiftui? - swiftui

I am trying to play different sounds from firebase storage and users will be able to play multiple sounds at the same time (max 5). These sounds has to be in loop until users stop them. I tried some of approaches from another answers but I faced with some bugs since they are not for multiple AVPlayers. Here is my code
ForEach($data.categoriesArray[i].sounds) { $sound in
let n = sound.number
Button(action:{
if(isItActiveArray[i][n] == false){
let storage = Storage.storage().reference(forURL: sound.soundURL)
storage.downloadURL{(url, error) in
if error != nil {
print(error)
} else{
sound.assignedPlayer = PlayerToAssign(array: isPlayerFull)
fullPlayersCount += 1
isPlayerFull[sound.assignedPlayer] = true
players[sound.assignedPlayer] = AVPlayer(url: url!)
players[sound.assignedPlayer].play()
}
}
isItActiveArray[i][n] = true
} else{
players[sound.assignedPlayer].pause()
fullPlayersCount -= 1
isPlayerFull[sound.assignedPlayer] = false
sound.assignedPlayer = -1
isItActiveArray[i][n] = false
}
}){
ZStack{
SoundView(sound: sound)
if(!userViewModel.isSubscriptionActive && i != 0){
Image("lock")
.resizable()
.scaledToFit()
.frame(width:35)
.position(x:100,y: 10)
}
}
}.opacity(isItActiveArray[i][n] ? 0.5: 1)
.disabled(fullPlayersCount < 5)
}

Related

Trouble looping with AVFoundation audio recorder & audioplayer

I am trying to make an app that records and loops an audio clip.
I have this function in my AudioPlayer struct:
func loop(nums:Int){
audioPlayer!.numberOfLoops = nums
}
And I am referencing it in my swiftUI struct. For some reason it crashes the app. Here is where I reference the function:
Button(action:{
if !canRecord && !edit && slot.beenRecorded == true{
if isInfinite{
audioPlayer.loop(nums: -1)
}
else{
audioPlayer.loop(nums: myInt)
}
if let recording = audioRecorder.recordings.first(where: { $0.fileURL.lastPathComponent == "\(index).m4a" }) {
self.audioPlayer.startPlayback(audio: recording.fileURL)
}
else {
print("No audio url was saved")
}
if audioPlayer.isPlaying == false {
print("audio is playing")
}
}
if canRecord && oneIsRecording == false{
slot.beenRecorded = true
UserDefaults.standard.set(true, forKey: slot.id)
isActive = true
}
})

Create ability to time limit tap on button with after week since last tap (SwiftUI)

On my app user can change it username. So not to increase firebase loads, i want to create time limit before last tap, (1 week i think will be ok), and in other time make this button gray color and not active with counter how many days remaining.
Here is my button code:
Button(action: {
let currentUser = Auth.auth().currentUser
if (currentUser != nil) {
self.loadingView = true
let id = currentUser!.uid
Ref.FIRESTORE_COLLECTION_USERS.document(id).updateData(["bio" : self.bio, "username" : self.name, "keywords" : self.name.splitStringToArray()]) { (err) in
//Конструкция: если err (ошибка) не nil, значит ест ьошибка и что-то пошло не так
// если nil, значит все хорошо
if let err = err {
//делаем что-то если ошибка
//например показываем надпись с сообщением об ошибке
print("Error updating document: \(err)")
self.loadingView = false
} else {
self.sessionStore.userSession?.bio = self.bio
self.sessionStore.userSession?.username = self.name
//то же самое только если все получилось
print("Document successfully updated")
if (self.imageData == nil) {
self.loadingView = false
self.showEditProfile = false
}
}
}
//save image
//Если новая картинка выбрана
if (self.imageData != nil && self.sessionStore.userSession != nil) {
//Сначала загружаем в хранилище, а затем ссылку на это фото обновляем в базе данных у пользователя
let storageAvatarUserId = Ref.STORAGE_AVATAR_USERID(userId: Auth.auth().currentUser!.uid)
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
//для био было
self.sessionStore.userSession?.bio = self.bio
//для логина - апдейт
self.sessionStore.userSession?.username = self.name
//cлушатель для обновления био
NotificationCenter.default.post(name: NSNotification.Name("update_profile_bio"), object: nil)
StorageService.updateAvatar(userId: self.sessionStore.userSession!.uid, username: self.sessionStore.userSession!.username, email: self.sessionStore.userSession!.email, imageData: self.imageData!, metaData: metadata, storageAvatarRef: storageAvatarUserId, onSuccses: {url in
if url != nil {
self.sessionStore.userSession?.profileImageUrl = url!
NotificationCenter.default.post(name: NSNotification.Name("update_profile_image"), object: nil)
self.loadingView = false
self.showEditProfile = false
} else {
self.loadingView = false
}
})
}
}
}) {
HStack {
Spacer()
Text(LocalizedStringKey("Save changes")).fontWeight(.bold).foregroundColor(Color.white)
Spacer()
}.padding().background(Color.black)
}.cornerRadius(5).shadow(radius: 10, x: 0, y: 10).padding()
So i want - when user press this button - make it avalible to tap after 7 days. And always use this rule.
Here is an extremely basic version of solving the problem. It has a bunch of architecture decisions you would have to make (I'll enumerate some) but it should get you started:
let interval : TimeInterval = 60 //number of seconds. 60 * 60 * 24 * 7 == one week
struct ContentView: View {
#AppStorage("buttonPressDate") var buttonPressDate : TimeInterval = -1
var isButtonDisabled : Bool {
buttonPressDate >= Date().timeIntervalSince1970 - interval
}
var nextButtonPressAvailable : TimeInterval {
buttonPressDate + interval - Date().timeIntervalSince1970
}
var body: some View {
VStack {
Button(action: {
buttonPressDate = Date().timeIntervalSince1970
}) {
Text("Button")
if isButtonDisabled {
Text("Next press in: \(Int(nextButtonPressAvailable)) sec")
}
}
.disabled(isButtonDisabled)
}
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
Concerns:
This only checks the buttonPressDate when UserDefaults is updated or when the view is reloaded. If you want the button to become available at other times, you'd probably want to set a scheduled event somehow (Timer, etc)
This is very low security -- a user could just alter UserDefaults to change the date. But, it gives you the foundation for how to do a more complex and secure setup.
This stores the time locally -- you didn't specify whether you want the date stored locally or on the server, but it would be easy to store this value in Firebase instead

Treating adjacent tracking areas as one contiguous area

I'm trying to present a UI of a title/less window when a mouse leaves a window's title or contentview, but not when moving from one to the other; in essence have the two tracking areas function as one (I resorted to this as I couldn't figure out how to create a single area when the title view is hidden):
override func mouseEntered(with theEvent: NSEvent) {
let hideTitle = (doc?.settings.autoHideTitle.value == true)
if theEvent.modifierFlags.contains(.shift) {
NSApp.activate(ignoringOtherApps: true)
}
switch theEvent.trackingNumber {
case closeTrackingTag:
Swift.print("close entered")
closeButton?.image = closeButtonImage
break
default:
Swift.print(String(format: "%# entered",
(theEvent.trackingNumber == titleTrackingTag
? "title" : "view")))
let lastMouseOver = mouseOver
mouseOver = true
updateTranslucency()
// view or title entered
if hideTitle && (lastMouseOver != mouseOver) {
updateTitleBar(didChange: !lastMouseOver)
}
}
}
override func mouseExited(with theEvent: NSEvent) {
let hideTitle = (doc?.settings.autoHideTitle.value == true)
switch theEvent.trackingNumber {
case closeTrackingTag:
Swift.print("close exited")
closeButton?.image = nullImage
break
default:
Swift.print(String(format: "%# exited",
(theEvent.trackingNumber == titleTrackingTag
? "title" : "view")))
let lastMouseOver = mouseOver
mouseOver = false
updateTranslucency()
if hideTitle && (lastMouseOver != mouseOver) {
updateTitleBar(didChange: lastMouseOver)
}
}
}
Additionally, there's a tracking rect on the close button to appear only when over. Anyway, from my tracer output I see the issue - mouse over window from beneath to over its title:
view entered
updateTitleBar
**view entered**
view exited
updateTitleBar
title entered
updateTitleBar
title exited
Note sure why I'm getting a second view entered event (view entered), but the movement out of the view and onto the adjacent title each triggers an updateTilebar() call which is visually stuttering - not remedied so far with animation:
fileprivate func docIconToggle() {
let docIconButton = panel.standardWindowButton(.documentIconButton)
if settings.autoHideTitle.value == false || mouseOver {
if let doc = self.document {
docIconButton?.image = (doc as! Document).displayImage
}
else
{
docIconButton?.image = NSApp.applicationIconImage
}
docIconButton?.isHidden = false
self.synchronizeWindowTitleWithDocumentName()
}
else
{
docIconButton?.isHidden = true
}
}
#objc func updateTitleBar(didChange: Bool) {
if didChange {
Swift.print("updateTitleBar")
if settings.autoHideTitle.value == true && !mouseOver {
NSAnimationContext.runAnimationGroup({ (context) -> Void in
context.duration = 1.0
panel.animator().titleVisibility = NSWindowTitleVisibility.hidden
panel.animator().titlebarAppearsTransparent = true
panel.animator().styleMask.formUnion(.fullSizeContentView)
}, completionHandler: {
self.docIconToggle()
})
} else {
NSAnimationContext.runAnimationGroup({ (context) -> Void in
context.duration = 1.0
panel.animator().titleVisibility = NSWindowTitleVisibility.visible
panel.animator().titlebarAppearsTransparent = false
panel.animator().styleMask.formSymmetricDifference(.fullSizeContentView)
}, completionHandler: {
self.docIconToggle()
})
}
}
}
So my question is about how to defer the actions when areas are adjacent.
They (titlebar & content view) are not siblings of each other so didn't think a hitTest() was doable but basically if I could tell if I was moving into the adjacent tracking area, I'd like it to be a no-op.
Any help with why animation isn't working would be a plus.
Not a true answer, but if you know the adjacent view's rect you can use the event's location to probe whether you'd want to ignore movements among adjacent views:
override func mouseExited(with theEvent: NSEvent) {
let hideTitle = (doc?.settings.autoHideTitle.value == true)
let location : NSPoint = theEvent.locationInWindow
switch theEvent.trackingNumber {
case closeTrackingTag:
Swift.print("close exited")
closeButton?.image = nullImage
break
default:
let vSize = self.window?.contentView?.bounds.size
// If we exit to the title bar area we're still "inside"
// and visa-versa, leaving title to content view.
if theEvent.trackingNumber == titleTrackingTag, let tSize = titleView?.bounds.size {
if location.x >= 0.0 && location.x <= (vSize?.width)! && location.y < ((vSize?.height)! + tSize.height) {
Swift.print("title -> view")
return
}
}
else
if theEvent.trackingNumber == viewTrackingTag {
if location.x >= 0.0 && location.x <= (vSize?.width)! && location.y > (vSize?.height)! {
Swift.print("view -> title")
return
}
}
mouseOver = false
updateTranslucency()
if hideTitle {
updateTitleBar(didChange: true)
}
Swift.print(String(format: "%# exited",
(theEvent.trackingNumber == titleTrackingTag
? "title" : "view")))
}
}

How to use elseif statement to show hidden text

New to Javascript, need some help with hidden panels.
I want the user to answer some questions in a radio button form which will have different values assigned to the checked answers. There will be 3 different options of results in hidden panels, one result will be displayed at the end depending on the score.
Link to full code and html here
function results() {
var lRisk = document.getElementById('lowRisk')
var mRisk = document.getElementById('mediumRisk')
var hRisk = document.getElementById('highRisk')
if ((score >= 0) && (score =< 15)) {
lRisk.style.display = 'inline';
} else if ((score >= 16) && (score <= 25)) {
mRisk.style.display = 'inline';
} else {
lRisk.style.display = 'inline';
}
Would be very grateful for any help/advice!
If you need to display only ONE control based on the score, you should make all of them hidden in the start then do your conditions.
function results() {
var lRisk = document.getElementById('lowRisk')
var mRisk = document.getElementById('mediumRisk')
var hRisk = document.getElementById('highRisk')
lRisk.style.display = 'none';
mRisk.style.display = 'none';
hRisk.style.display = 'none';
if (score >= 0 && score <= 15) {
lRisk.style.display = 'inline';
} else if (score >= 16 && score <= 25) {
mRisk.style.display = 'inline';
} else {
hRisk.style.display = 'inline';
}
}

Why is this not saving?

I am trying to save a simple piece of information using NSUserdefaults. I am trying to save a SKSprite to have an alpha of 1. Here is how I am doing it.
First scene: Level select (sprite alpha is 0.2)
When user completes Level: (edit sprite in Level Select to equal one)
GameViewController:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = levelselectscene {
// Set the scale mode to scale to fit the window
scene.scaleMode = .fill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
override var shouldAutorotate: Bool {
return true
}
Level Select:
override func didMove(to view: SKView) {
if unlockLevelTwoButton == true {
levelselectscene?.childNode(withName: "LevelTwoButton")?.alpha = 1
UserDefaults.standard.set(unlockLevelTwoButton, forKey: "LevelTwoUnlocked")
print("I got this far")
}
}
Level One:
func didBegin(_ contact: SKPhysicsContact) {
var bodyA = contact.bodyA
var bodyB = contact.bodyB
let threeStars = SKScene(fileNamed: "LevelCompleted3Star")
let fadeAction = SKAction.fadeAlpha(by: 1, duration: 0.45)
if bodyA.categoryBitMask == 1 && bodyB.categoryBitMask == 2 || bodyA.categoryBitMask == 2 && bodyB.categoryBitMask == 1{
print("TEST")
levelOneCompleted() //islevelonecompleted
unlockLevelTwoButton = true
//3 stars
threeStars?.scaleMode = .fill
self.view?.presentScene(threeStars!, transition: .fade(withDuration: 0.3))
}
3 Stars:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if isLevelOneCompleted == true{
unlockLevelTwoButton = true
UserDefaults.standard.set(isLevelOneCompleted, forKey: "LevelOne")
UserDefaults.standard.synchronize()
levelselectscene?.scaleMode = .fill
levelselectscene?.childNode(withName: "levelTwoButton")?.alpha = 1
self.view?.presentScene(levelselectscene)
}
To me, it looks like the information should save. What am I doing wrong? I also have the keys set to retrieve:
if let z = UserDefaults.standard.object(forKey: "LevelTwoButton")
{
unlockLevelTwoButton = z as! Bool
}
Can't figure out why it's not saving!
Based on the code you've shown, you are saving it with one name, and retrieving it with a different name (LevelTwoUnlocked) vs (LevelTwoButton)