Am I able to use the value of a variable to call a variable within my struct? - variables

I have sounds stored for different events within the struct that the user will be able to change, and was hoping i could send the function a string to select a song for a specific event.
my function call would look like this:
func playSound(audio: Audio, soundSelect: String = "startSound"){
if let path = Bundle.main.path(forResource: audio.\(soundSelect), ofType: audio.soundType){
do {
audioPlayer = try! AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
audioPlayer?.play()
}catch{
print("ERROR: Could not find and play the sound file!")
}
and my struct would look something like this:
struct Audio {
var startSound: String = "happyMusic"
var endSound: String = "sadMusic"
var soundType: String = "mp3"
}
as above i tried string interpolation which didn't seem to work I got the error
"Expected member name following '.'
What I expected was for "audio.\(soundSelect)" to be read like "audio.startSound"

You cannot build variable names at runtime because all variable names are evaluated at compile time.
But if you define the type of the parameter as a KeyPath you are able to address different properties in the struct
func playSound(audio: Audio, soundSelect: KeyPath<Audio,String> = \.startSound) {
if let url = Bundle.main.url(forResource: audio[keyPath: soundSelect], withExtension: audio.soundType) {
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.play()
} catch {
print("ERROR: Could not find and play the sound file!", error)
}
}
}
Notes:
If you implement a do - catch block handle (removing the question mark in try?) and print the error rather than just a meaningless string literal.
Bundle provides an URL related API which avoids the extra URL(fileURLWithPath call
A still swiftier syntax is to make the function throw and hand over the error(s) to the caller
func playSound(audio: Audio, soundSelect: KeyPath<Audio,String> = \.startSound) throws {
if let url = Bundle.main.url(forResource: audio[keyPath: soundSelect], withExtension: audio.soundType) {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.play()
} else {
throw URLError(.badURL)
}
}

Related

SwiftUI OpenWeatherApi App show no content

Im new to swift development and I can't make myself pass through this. I run my app and by pressing the "action" button I see no change on my text which should be the current temperature. And It's giving me an error "nw_protocol_get_quic_image_block_invoke dlopen libquic failed"
My code:
struct ContentView: View {
#State var temp = Int()
func CallApi(){
let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=budapest&appid=#########################################")
URLSession.shared.dataTask(with: url!){data, response, error in
if let data = data {
if let DecodedData = try? JSONDecoder().decode(Api.self, from: data){
self.temp = DecodedData.main.temp
}
}
}.resume()
}
struct Api: Codable, Hashable{
var main: DataStructre
}
struct DataStructre: Codable, Hashable {
var temp: Int
}
var body: some View {
Text("\(temp)")
Button("Idojaras"){CallApi()}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you use do/try/catch instead of try?, you can get a useful error message.
do {
let DecodedData = try JSONDecoder().decode(Api.self, from: data)
self.temp = DecodedData.main.temp
} catch {
print(error)
}
In this case, I get:
Parsed JSON number <304.31> does not fit in Int.
Changing the type of temp (in both the #State variable and the DataStructre) fixes the issue.
In general, I'd recommend pasting your JSON into app.quicktype.io, which will give you generated models with the correct types/structures.
Slightly unrelated to your issue, but you may want to look into using dataTaskPublisher and Combine in SwiftUI rather than dataTask (https://developer.apple.com/documentation/foundation/urlsession/processing_url_session_data_task_results_with_combine). Also, function names in Swift are generally lowercased.

How to create a static pointer variable to itself in Swift?

In Objective-C I often use the pattern of using a static void* as an identification tag. At times these tags are only used within that function/method, hence it's convenient to place the variable inside the function.
For example:
MyObscureObject* GetSomeObscureProperty(id obj) {
static void* const ObscurePropertyTag = &ObscurePropertyTag;
MyObscureObject* propValue = objc_getAssociatedObject(id,ObscurePropertyTag);
if(!propValue) {
propValue = ... // lazy-instantiate property
objc_setAssociatedObject(obj,ObscurePropertyTag,propValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
return propValue;
}
The question is, how to write the ObscurePropertyTag private-constant-pointer-to-itself in Swift? (Preferrably 2.1 but future already-announced versions should be okay)
I've looked around and it seems that I have to put this ObscurePropertyTag as a member variable and there doesn't seem to be a way around it.
Unlike (Objective-)C, you cannot take the address of an
uninitialized variable in Swift. Therefore creating a self-referencing
pointer is a two-step process:
Swift 2:
var ptr : UnsafePointer<Void> = nil
withUnsafeMutablePointer(&ptr) { $0.memory = UnsafePointer($0) }
Swift 3:
var ptr = UnsafeRawPointer(bitPattern: 1)!
ptr = withUnsafePointer(to: &ptr) { UnsafeRawPointer($0) }
For your purpose, is it easier to use the address of a global variable with &, see for
example
Is there a way to set associated objects in Swift?.
If you want to restrict the scope of the "tag" to the function itself
then you can use a static variable inside a local struct. Example:
func obscureProperty(obj : AnyObject) -> MyObscureObject {
struct Tag {
static var ObscurePropertyTag : Int = 0
}
if let propValue = objc_getAssociatedObject(obj, &Tag.ObscurePropertyTag) as? MyObscureObject {
return propValue
}
let propValue = ... // lazy instantiate property value
objc_setAssociatedObject(obj, &Tag.ObscurePropertyTag,propValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return propValue
}
Try this:
var GetSomeObscureProperty: MyObscureObject = nil
withUnsafePointer(& GetSomeObscureProperty) {
GetSomeObscureProperty = MyObscureObject($0)
}
In short
let GetSomeObscureProperty = UnsafePointer<()>()

AnyObject to NsDictionery

How can i convert this javascript data to ios compatible data and put it in a dictionary, when i do something like this newCustomer["cvc_check"] etc nothing comes up i get a nil, l am trying to get data from parse PFCloud.callFunctioninBackground which is calling a stripe.com api to get those results
func hasToken(token: STPToken!) {
var parameters = ["cardToken":token.tokenId,
"objectId":PFUser.currentUser().objectId]
PFCloud.callFunctionInBackground("createCustomer", withParameters:parameters) {
(results: AnyObject!, error: NSError!) -> Void in
if !error {
var newCustomer = results as Dictionary<String, AnyObject>
println(newCustomer["cvc_check"]) // This gives me a nil
self.successfulPayment()
} else {
let message = error.userInfo["error"] as NSString
self.hasError("\(message) Please try agains.")
}
}
I think the problem is with the line:
var newCustomer = results as Dictionary<String, AnyObject>
As you said in your comment, results is an Array, so this line should be:
var newCustomer = results as Array<Dictionary<String, AnyObject>>
Does that help?

Core-Graphics (iPhone) -Can we know whether a CGPath is closed?

Is there any workaround to know where a CGpath (created using an array of co-ordinates) is closed.
I was not able to find any suitable method in the header CGPath.
I wish to track the path traced by user .If its a closed loop, then i wish to extract that part from the context. Something like masking or clipping the context but it should be user tracked clipping.
Thanks!
In fact, a path can consist of multiple subpaths. A new subpath is created when you move the path's current point without connecting the two points. For the path to be closed, all its subpaths must in fact be closed.
extension CGPath {
/// Note that adding a line or curve to the start point of the current
/// subpath does not close it. This can be visualized by stroking such a
/// path with a line cap of `.butt`.
public var isClosed: Bool {
var completedSubpathsWereClosed = true
var currentSubpathIsClosed = true
self.applyWithBlock({ pointer in
let element = pointer.pointee
switch element.type {
case .moveToPoint:
if !currentSubpathIsClosed {
completedSubpathsWereClosed = false
}
currentSubpathIsClosed = true
case .addLineToPoint, .addQuadCurveToPoint, .addCurveToPoint:
currentSubpathIsClosed = false
case .closeSubpath:
currentSubpathIsClosed = true
}
})
return completedSubpathsWereClosed && currentSubpathIsClosed
}
}
Just in case you've been stuck on this for the last 4 1/2 years, here is how to do it in Swift 3. I have borrowed heavily from this answer. I really just added the isClosed() function.
extension CGPath {
func isClosed() -> Bool {
var isClosed = false
forEach { element in
if element.type == .closeSubpath { isClosed = true }
}
return isClosed
}
func forEach( body: #convention(block) (CGPathElement) -> Void) {
typealias Body = #convention(block) (CGPathElement) -> Void
let callback: #convention(c) (UnsafeMutableRawPointer, UnsafePointer<CGPathElement>) -> Void = { (info, element) in
let body = unsafeBitCast(info, to: Body.self)
body(element.pointee)
}
print(MemoryLayout.size(ofValue: body))
let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self)
self.apply(info: unsafeBody, function: unsafeBitCast(callback, to: CGPathApplierFunction.self))
}
}
And assuming path is a CGPath or CGMutablePath, here is how you use it:
path.isClosed()

How to play m3u8 encrypted playlists by providing key file separately?

I have a m3u8 playlist file(lets call it prime), which points to another playlist file which in turn has the ts URLs with the key file URL. Using MPMoviePlayer I can currently play the prime m3u8 file.
The segments are encrypted with AES-128 bit encryption and the key file is in the final m3u8 file. Is there a way that I can supply the final m3u8 file and tell the app to use a local key file to decrypt the video, so I don't have to publish the key file publicly.
This is somewhat related to this SO question
I have implemented something similar to this. What we did was:
Encrypt each segment of Live stream segment at runtime with a JWT
Token that has a combination of key value pairs and time stamp for
validation.
Our server knows how to decrypt this key. and when the
decrypted data is valid, the server responds with a .ts file and
hence the playback becomes secure.
Here is the complete working code with steps mentioned:
//Step 1,2:- Initialise player, change the scheme from http to fakehttp and set delete of resource loader. These both steps will trigger the resource loader delegate function so that we can manually handle the loading of segments.
func setupPlayer(stream: String) {
operationQ.cancelAllOperations()
let blckOperation = BlockOperation {
let currentTStamp = Int(Date().timeIntervalSince1970 + 86400)//
let timeStamp = String(currentTStamp)
self.token = JWT.encode(["Expiry": timeStamp],
algorithm: .hs256("qwerty".data(using: .utf8)!))
self.asset = AVURLAsset(url: URL(string: "fake\(stream)")!, options: nil)
let loader = self.asset?.resourceLoader
loader?.setDelegate(self, queue: DispatchQueue.main)
self.asset!.loadValuesAsynchronously(forKeys: ["playable"], completionHandler: {
var error: NSError? = nil
let keyStatus = self.asset!.statusOfValue(forKey: "playable", error: &error)
if keyStatus == AVKeyValueStatus.failed {
print("asset status failed reason \(error)")
return
}
if !self.asset!.isPlayable {
//FIXME: Handle if asset is not playable
return
}
self.playerItem = AVPlayerItem(asset: self.asset!)
self.player = AVPlayer(playerItem: self.playerItem!)
self.playerView.playerLayer.player = self.player
self.playerLayer?.backgroundColor = UIColor.black.cgColor
self.playerLayer?.videoGravity = AVLayerVideoGravityResizeAspect
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd(notification:)), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: self.playerItem!)
self.addObserver(self, forKeyPath: "player.currentItem.duration", options: [.new, .initial], context: &playerViewControllerKVOContext)
self.addObserver(self, forKeyPath: "player.rate", options: [.new, .old], context: &playerViewControllerKVOContext)
self.addObserver(self, forKeyPath: "player.currentItem.status", options: [.new, .initial], context: &playerViewControllerKVOContext)
self.addObserver(self, forKeyPath: "player.currentItem.loadedTimeRanges", options: [.new], context: &playerViewControllerKVOContext)
self.addObserver(self, forKeyPath: "player.currentItem.playbackLikelyToKeepUp", options: [.new], context: &playerViewControllerKVOContext)
self.addObserver(self, forKeyPath: "player.currentItem.playbackBufferEmpty", options: [.new], context: &playerViewControllerKVOContext)
})
}
operationQ.addOperation(blckOperation)
}
//Step 2, 3:- implement resource loader delegate functions and replace the fakehttp with http so that we can pass this m3u8 stream to the parser to get the current m3u8 in string format.
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
var url = loadingRequest.request.url?.absoluteString
let contentRequest = loadingRequest.contentInformationRequest
let dataRequest = loadingRequest.dataRequest
//Check if the it is a content request or data request, we have to check for data request and do the m3u8 file manipulation
if (contentRequest != nil) {
contentRequest?.isByteRangeAccessSupported = true
}
if (dataRequest != nil) {
//this is data request so processing the url. change the scheme to http
url = url?.replacingOccurrences(of: "fakehttp", with: "http")
if (url?.contains(".m3u8"))!
{
// do the parsing on background thread to avoid lags
// step 4:
self.parsingHandler(url: url!, loadingRequest: loadingRequest, completion: { (success) in
return true
})
}
else if (url?.contains(".ts"))! {
let redirect = self.generateRedirectURL(sourceURL: url!)
if (redirect != nil) {
//Step 9 and 10:-
loadingRequest.redirect = redirect!
let response = HTTPURLResponse(url: URL(string: url!)!, statusCode: 302, httpVersion: nil, headerFields: nil)
loadingRequest.response = response
loadingRequest.finishLoading()
}
return true
}
return true
}
return true
}
func parsingHandler(url: String, loadingRequest: AVAssetResourceLoadingRequest, completion:((Bool)->Void)?) -> Void {
DispatchQueue.global(qos: .background).async {
var string = ""
var originalURIStrings = [String]()
var updatedURIStrings = [String]()
do {
let model = try M3U8PlaylistModel(url: url)
if model.masterPlaylist == nil {
//Step 5:-
string = model.mainMediaPl.originalText
let array = string.components(separatedBy: CharacterSet.newlines)
if array.count > 0 {
for line in array {
//Step 6:-
if line.contains("EXT-X-KEY:") {
//at this point we have the ext-x-key tag line. now tokenize it with , and then
let furtherComponents = line.components(separatedBy: ",")
for component in furtherComponents {
if component.contains("URI") {
// Step 7:-
//save orignal URI string to replaced later
originalURIStrings.append(component)
//now we have the URI
//get the string in double quotes
var finalString = component.replacingOccurrences(of: "URI=\"", with: "").replacingOccurrences(of: "\"", with: "")
finalString = "\"" + finalString + "&token=" + self.token! + "\""
finalString = "URI=" + finalString
updatedURIStrings.append(finalString)
}
}
}
}
}
if originalURIStrings.count == updatedURIStrings.count {
//Step 8:-
for uriElement in originalURIStrings {
string = string.replacingOccurrences(of: uriElement, with: updatedURIStrings[originalURIStrings.index(of: uriElement)!])
}
//print("String After replacing URIs \n")
//print(string)
}
}
else {
string = model.masterPlaylist.originalText
}
}
catch let error {
print("Exception encountered")
}
loadingRequest.dataRequest?.respond(with: string.data(using: String.Encoding.utf8)!)
loadingRequest.finishLoading()
if completion != nil {
completion!(true)
}
}
}
func generateRedirectURL(sourceURL: String)-> URLRequest? {
let redirect = URLRequest(url: URL(string: sourceURL)!)
return redirect
}
Implement Asset Resource Loader Delegate for custom handling of streams.
Fake the scheme of live stream so that the Resource loader delegate gets called (for normal http/https it doesn't gets called and player tries to handle the stream itself)
Replace the Fake Scheme with Http scheme.
Pass the stream to M3U8 Parser to get the m3u8 file in plain text format.
Parse the plain string to find EXT-X-KEY tags in the current string.
Tokenise the EXT-X-KEY line to get to the "URI" method string.
Append JWT token separately made, with the current URI method in the m3u8.
Replace all instances of URI in the current m3u8 string with the new token appended URI string.
Convert this string to NSData format
Feed it to the player again.
Hope this helps!
Yes -- You can modify the final m3u8 file before passing it to the player. For example, change the KEY lines to refer to http://localhost/key. Then you would want to run a local http server such as cocoahttpserver to deliver the key to the video player.