Trouble looping with AVFoundation audio recorder & audioplayer - swiftui

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
}
})

Related

How to loop multiple AVPlayers in 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)
}

SwiftUI ObservableObject and Published issue

here is something that keeps me awake for three days already: I'm writing a little app that connects via BlueTooth to an Arduino. To get visual feedback about the connection state and the transmitted data, I use a view that allows me to connect/disconnect as well as shows me the state and data:
VStack {
Text("Glove Training App")
.font(.title)
HStack {
Button(action: { MyBluetoothManager.shared.scan() }) {
Text("Connect")
.padding(30)
}
Text(" | ")
Button(action: { MyBluetoothManager.shared.disconnect()}) {
Text("Disconnect")
.padding(30)
}
}
Text(manager.stateChange)
.font(.subheadline)
.padding(.bottom, 30)
Text(peripheral.transmittedString)
.font(.subheadline)
.padding(.bottom, 30)
}
}
In a separate file I have all the BT management:
class MyBluetoothManager: NSObject, ObservableObject {
#Published var stateChange: String = "Initializing..." {
willSet { objectWillChange.send() }
}
static let shared = MyBluetoothManager()
let central = CBCentralManager(delegate: MyCentralManagerDelegate.shared,
queue: nil, options: [
CBCentralManagerOptionRestoreIdentifierKey: restoreIdKey,
])
(...)
func setConnected(peripheral: CBPeripheral) {
(...)
state = .connected(peripheral)
self.stateChange = "Connected"
print("Connected")
}
}
class MyPeripheralDelegate: NSObject, ObservableObject, CBPeripheralDelegate {
let objectWillChange = ObservableObjectPublisher()
var transmittedString: String = "No data" {
willSet { objectWillChange.send()
}
}
func peripheral(_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
(...)
let rxData = characteristic.value
if let str = NSString(data: rxData!, encoding: String.Encoding.utf8.rawValue) as String? {
print(str)
self.transmittedString = str
let measurement = str.components(separatedBy: "|")
(...)
} else {
print("not a valid UTF-8 sequence")
}
}
}
The values are initially set correctly, but then never updated. In the terminal I can see the printed values and the app works otherwise as expected. I'm on the latest version of XCode.
I looked at several tutorials, and this seems to be tricky. Any help would be highly appreciated.
Cheers,
Christian
EDIT: Here is the full BluetoothManager class (not my code mostly but works fine):
class MyBluetoothManager: NSObject, ObservableObject {
#Published var stateChange: String = "Initializing..." {
willSet { objectWillChange.send() }
}
static let shared = MyBluetoothManager()
let central = CBCentralManager(delegate: MyCentralManagerDelegate.shared,
queue: nil, options: [
CBCentralManagerOptionRestoreIdentifierKey: restoreIdKey,
])
var state = State.poweredOff
enum State {
case poweredOff
case restoringConnectingPeripheral(CBPeripheral)
case restoringConnectedPeripheral(CBPeripheral)
case disconnected
case scanning(Countdown)
case connecting(CBPeripheral, Countdown)
case discoveringServices(CBPeripheral, Countdown)
case discoveringCharacteristics(CBPeripheral, Countdown)
case connected(CBPeripheral)
case outOfRange(CBPeripheral)
var peripheral: CBPeripheral? {
switch self {
case .poweredOff: return nil
case .restoringConnectingPeripheral(let p): return p
case .restoringConnectedPeripheral(let p): return p
case .disconnected: return nil
case .scanning: return nil
case .connecting(let p, _): return p
case .discoveringServices(let p, _): return p
case .discoveringCharacteristics(let p, _): return p
case .connected(let p): return p
case .outOfRange(let p): return p
}
}
}
func scan() {
guard central.state == .poweredOn else {
self.stateChange = "Cannot scan, BT is not powered on"
print("Cannot scan, BT is not powered on")
return
}
central.scanForPeripherals(withServices: [myDesiredServiceId], options: nil)
state = .scanning(Countdown(seconds: 10, closure: {
self.central.stopScan()
self.state = .disconnected
self.stateChange = "Scan timed out"
print("Scan timed out")
}))
}
func disconnect(forget: Bool = false) {
if let peripheral = state.peripheral {
central.cancelPeripheralConnection(peripheral)
}
if forget {
UserDefaults.standard.removeObject(forKey: peripheralIdDefaultsKey)
UserDefaults.standard.synchronize()
}
self.stateChange = "Disconnected"
state = .disconnected
}
func connect(peripheral: CBPeripheral) {
central.connect(peripheral, options: nil)
state = .connecting(peripheral, Countdown(seconds: 10, closure: {
self.central.cancelPeripheralConnection(peripheral)
self.state = .disconnected
self.stateChange = "Connect timed out"
print("Connect timed out")
}))
}
func discoverServices(peripheral: CBPeripheral) {
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.discoverServices([myDesiredServiceId])
state = .discoveringServices(peripheral, Countdown(seconds: 10, closure: {
self.disconnect()
self.stateChange = "Could not discover services"
print("Could not discover services")
}))
}
func discoverCharacteristics(peripheral: CBPeripheral) {
guard let myDesiredService = peripheral.myDesiredService else {
self.disconnect()
return
}
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.discoverCharacteristics([myDesiredCharacteristicId],
for: myDesiredService)
state = .discoveringCharacteristics(peripheral, Countdown(seconds: 10,
closure: {
self.disconnect()
self.stateChange = "Could not discover characteristics"
print("Could not discover characteristics")
}))
}
func setConnected(peripheral: CBPeripheral) {
guard let myDesiredCharacteristic = peripheral.myDesiredCharacteristic
else {
self.stateChange = "Missing characteristic"
print("Missing characteristic")
disconnect()
return
}
UserDefaults.standard.set(peripheral.identifier.uuidString,
forKey: peripheralIdDefaultsKey)
UserDefaults.standard.synchronize()
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.setNotifyValue(true, for: myDesiredCharacteristic)
state = .connected(peripheral)
self.stateChange = "Connected"
print("Connected")
}
}
Button(action: { MyBluetoothManager.shared.scan() }) {
Text("Connect")
.padding(30)
}
Text(" | ")
Button(action: { MyBluetoothManager.shared.disconnect()}) {
Text("Disconnect")
.padding(30)
}
}
Text(manager.stateChange) << why don't you use MyBluetoothManager.shared here ? is there a second instance? this might be the error...but unfortunately you just showed us a small piece of code...

Custom Checkbox Category Selector

I want to filter my Data Array by different categories. It is working, but it should pick the rows of multiple categories. if multiple checkboxes are checked. How can I implement this code?
Here is my code:
check_cells = function() {
var values = [];
if (document.getElementById("checkbox_pouch").checked == true) {
values.push('pouch');
}
if (document.getElementById("checkbox_18650").checked == true) {
values.push('18650');
}
if (document.getElementById("checkbox_21700").checked == true) {
values.push('21700');
}
if (document.getElementById("checkbox_pouch").checked == false && document.getElementById("checkbox_18650").checked == false && document.getElementById("checkbox_21700").checked == false) {
values.push('empty');
}
if (values.length > 0) {
view.setRows(data.getFilteredRows([{
column:2,
test: function (value) {
return (values.indexOf(value) > -1);
}
}]));
}
dashboard.draw(view, drawOptions);
}
else {
view.setRows(data.getFilteredRows([{column:2}]));
dashboard.draw(view);
}
}
var view = new google.visualization.DataView(data);
var drawOptions = {
showRowNumber: false,
allowHtml: true,
};
// Inititial Draw of the dashboard.
dashboard.draw(view, drawOptions);
when filtering on multiple values,
you will need to combine those into one filter setting.
the data view will not let you have multiple filters on the same column.
in this case, you can use the test callback function, rather than the value key.
here, an array is used to gather the values,
and the test callback to filter the column...
check_pouch = function() {
var values = [];
if (document.getElementById("checkbox_pouch").checked == true) {
values.push('pouch');
}
if (document.getElementById("checkbox_18650").checked == true) {
values.push('18650');
}
if (values.length > 0) {
view.setRows(data.getFilteredRows([{
column:2,
test: function (value) {
return (values.indexOf(value) > -1);
}
}]));
}
dashboard.draw(view);
}

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")))
}
}

Deletebackward() Swift 3

DeleteBackward() deletes only one character, is there any way to keep on deleting backwards ?
I am using emojiKeyboard and I have a delete emoticon. I detect the emoji being the delete emoticon and I call
if emoticon.isDelete{
deleteBackward()
return
}
Update:
Steven's solution works on buttons but not on my UITextView. Will try and find out why. I have tried having the addGestureRecognizer in ViewWillAppear as well as ViewDidLoad.
This should get you started, didn't test but should do the trick.
fileprivate var timer = Timer()
fileprivate var textField = UITextField() //change to your field
override func viewDidLoad() {
super.viewDidLoad()
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPress(_:)))
textField.addGestureRecognizer(longPress)
}
func longPress(_ guesture: UILongPressGestureRecognizer) {
if guesture.state == UIGestureRecognizerState.began {
longPressBegun(guesture)
} else if guesture.state == UIGestureRecognizerState.changed {
//longPressStateChanged(guesture)
} else if guesture.state == UIGestureRecognizerState.ended {
longPressEnded()
} else if guesture.state == UIGestureRecognizerState.cancelled {
longPressCancelled()
}
}
func longPressBegun(_ guesture: UILongPressGestureRecognizer) {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(repeatAction), userInfo: nil, repeats: true)
}
func longPressEnded() {
timer.invalidate()
}
func longPressCancelled() {
timer.invalidate()
}
func repeatAction() {
deleteBackward()
}