I'm building out an audio tour component of an app, and I've hit a rut on organizing and calling data. I'm totally green to iOS development; coming from a Ruby/Rails background.
Class names in bold: The goal is to trigger specific information at each tour Stop. Each Stop, will be an AudioClip and play a soundbite, or it launches a Trail to guide the user to the next Stop, or it will flash a Video modal with video, etc. And at each stop, there need to be callbacks once reached. For example, if I arrive at the art museum, I would hear a clip about the Mona Lisa and then an image of the portrait would display on the phone. But all stops are not created equal, so even though the art meseum stop has an audio file to play and an image, the stop at the park may just display a walking trail with nothing else.
I have the app set up like this:
class Stop: NSObject {
func execStop() {
// do stuff
}
}
class AudioClip: Stop { }
class Trail: Stop { }
class StopImage: Stop { }
All of these tours need to be triggered in consequential order, and the next tour stop shouldn't be shown if the current stop hasn't been reached. The best way I could come up with is something like this:
// Each stop defined as its own class
class Museum: AudioClip {
override func execStop() {
playAudio(name: "AudioFile.m4a")
showImage(name: "MonaLisa.jpg")
}
}
class Park: Trail {
override func execStop() {
addRoute([Array, of, coordinates])
}
}
class Tour: NSObject {
var stops: [Int: AnyObject] = [:]
func resume() -> Void {
let active = nextAvailableStopLookup()
let currentStop = self.stops[(active as Int)] as! Stop
return currentStop.execStop()
}
}
// Individual tours, i.e. Museum, Resturant, Hike
class MuseumTour: Tour {
var TourPoints = [
1: Museum(...)
2: Park(...)
]
}
And then in the ViewController, the current Tour would be activated and based on the last stop completed, the next one would be queued.
override func viewDidLoad() {
let queueStop: Void = MuseumTour().resume()
}
The problem
This seems like a terrible way to set up an app with a lot of overhead. Each stop is a new class, and there could be upwards of 60 stops, which is 60 separate classes. The callback is the kicker - inherited-class functions (like func playAudio on AudioClip) are unavailable in the MuseumTour class. And I'm such a novice, I don't know how exactly to trigger dynamic closures.
Is there a better way of organizing and calling this type of data?
What you are doing certainly seems very silly in any language.
As far as I can tell, and to the extent that I was able to stay awake through your description, a "stop" is just a sequence of things to do (along with a pointer to another "stop"? I can't quite tell from your description). So it seems to me that a Stop is an instance of the Stop class (or, in Swift, more likely a struct) and that's all it is; there is no need for all these classes.
Your "I don't know how exactly to trigger dynamic closures" is quite telling, since an array of functions is exactly, it seems to me, what you want a "stop" to have. Maybe your first step should be to "stop" (sorry) and learn Swift, where functions are first-class citizens and so an array of them is perfectly normal.
In my very annoying app "99 Bottles", which sings "99 Bottles of Beer on the Wall", at each line of the song, we perform one or more actions, such as taking down a bottle, or removing the bottle from the screen, and we have to know what line of the song to sing. In my app, therefore, a Phrase struct consists of what to sing along with a list of actions to perform along with it:
struct Phrase {
let sound : String // name of sound file
let actions : [() -> ()] // what else to do
}
The song as a whole is thus nothing but an array of Phrase instances. We pop the first Phrase off the array, sing the sound, and call the actions functions in sequence:
let instruction = self.stages.removeAtIndex(0) // a Phrase
self.player.play(instruction.sound) // sing
for action in instruction.actions {
action() // perform the action
}
This sounds quite similar to what you are after.
(Of course, you say you are from a Ruby background; everything I just said would be equally true in Ruby. The notion of a function as a first-class citizen should come as no surprise to you.)
Related
I have 2 delegate methods that are being called by notifications from a 3rd party library.
Method 1:
mediaContentWasUpdated()
Method 2:
adMediaDidBeginPlaying()
In Method 1, a key variable (adDuration) is set from a parameter that is passed in with the notification. As far as I can see this is the only place to get this information.
In Method 2, we check the adDuration and if it is greater than 0 then we update the UI to reflect that we are in fact play an ad.
A bug has appeared where sometimes these two methods are called in the wrong order. Meaning the adDuration is not set and Method 2 thinks there is no ad media to be played and does not update the UI accordingly.
My current attempt at a solution is to make adDuration optional and use an NSCondition to cause Method 2 to wait for Method 1 to set adDuration and then proceed.
var adDuration : Double?
let condition = NSCondition()
func mediaContentWasUpdated(notification: NSNotificiation) {
condition.lock()
if(notificationHasAdDurationInfo(notification)) {
self.adDuration = getAdDuration(notification)
condition.signal()
}
condition.unlock()
}
func adMediaDidBeginPlaying(notification: NSNotification) {
condition.lock()
while adDuration == nil {
condition.wait()
}
if adDuration! > Double(0) {
updateUIForAd()
}
condition.unlock()
}
This is my first time trying something like this and I worry I am doing something wrong. I also have some concerns about locking and unlocking threads needlessly (which would happen in a well timed run, or if there were no ad content to be played).
Outside factors are hindering my ability to test and I wanted to get some input to see if I am heading in the right direction while I wait for those issues to be resolved.
Your discussion of NSCondition got me on the same track with you, and I built two or three solutions using DispatchGroup (which is the better tool for this), but they always had little corner cases that could behave badly, and didn't really capture the intent.
(If you're interested in the DispatchGroup solutions, they're of the form: call .enter() in init, call .leave() when the duration comes in, call notify() when the playing starts. It works fine, but it introduces corner cases that can crash, just like NSCondition.)
Getting back to the real intent:
Update the UI when the duration is known and the ad has started playing.
There's no concurrency going on here. So pulling out GCD is not just overkill; it actually makes things worse because it introduces lots of complicated corner cases.
So I thought about how I'd have solved this back before GCD. And the answer is obvious: just check if you have the data you want, and then do the thing. (Reading through the comments, I see Paulw11 pointed this out as well.)
Personally I like to pull this kind of thing into its own type to make things more self-contained. I hate some of the names here, but the idea should be clear:
class AdPlayer {
private var readyToPlay = false
private var duration: Double = 0.0
private let completion: (Double) -> Void
func setDuration(from notification: Notification) {
if(notificationHasAdDurationInfo(notification)) {
duration = getAdDuration(notification)
}
playIfReady()
}
func play() {
readyToPlay = true
playIfReady()
}
private func playIfReady() {
if duration > 0 && readyToPlay {
completion(duration)
}
}
init(completion: #escaping (Double) -> Void) {
self.completion = completion
}
}
When you set each thing, see if you're ready to update, and if so, update. I've gotten rid of the optional as well, since I believe the intent is "0 duration is always wrong." But you could use an Optional so you could detect actually receiving a 0 from the notification.
With that, you just set up a player property:
player = AdPlayer(completion: updateUIForAd)
(Note that the above might be creating a retain loop, depending on what updateUIForAd is; you may need a [weak self] closure or the like here.)
And then update it as needed:
func mediaContentWasUpdated(notification: NSNotificiation) {
player.setDuration(from: notification)
}
func adMediaDidBeginPlaying(notification: NSNotification) {
player.play()
}
A big advantage of creating the AdPlayer type is that it's easy to reset the system when the ad is done (or if something goes wrong). Just throw away the whole object and create another one.
First, I'd like to mention, that I'm a swift beginner, and this is my first programming experience ever. So the questions may sounds super obvious to some of you... Thanks you for your understanding :)
There's something I don't understand when accessing a superclass function with the subclass overrided function of the same name.
This is involving a "special type" as mentionned in the tuto I'm currently following :
"The play(_:) method returns a String to be played. You might wonder
why you would bother creating a special Music type, instead of just
passing along a String array of notes. This provides several
advantages: Creating Music helps build a vocabulary, enables the
compiler to check your work, and creates a place for future
expansion."
https://www.raywenderlich.com/160728/object-oriented-programming-swift
Considering the following code :
class Music {
let notes: [String]
init(notes: [String]) {
self.notes = notes
}
func prepared() -> String {
return notes.joined(separator: " ")
}
}
class Instrument {
let model: String
init(model: String) {
self.model = model
}
func play(_ music: Music) -> String {
return music.prepared()
}
}
class Piano: Instrument {
let hasPedals: Bool
init(hasPedals: Bool, model: String) {
self.hasPedals = hasPedals
super.init(model: model)
}
override func play(_ music: Music) -> String {
let preparedNotes = super.play(music)
return "Piano playing \(preparedNotes)"
}
}
What I understand is :
1) The class Music allows me to create a String of note.
2) This requires an input (using the initializer) that will be an Array
3) This input is then converted with .joined(separator: " ") to a String
4) The class Instrument has a func play(_ music: Music) that accept the class Music as an input. The parameter name is music:
5) The parameter Music can be set by creating an instance of the Music class (as mentioned in point 2)
6) The class Piano: Instrument has an override func play(_ music: Music)
7) By adding let preparedNotes = super.play(music) we are accessing the parent class func play(_ music: Music) (just for the fun of it, because it don't seems to me that we add any changes right ?)
- First question : as it seems to be possible to use a class as parameter for a function, can anyone shows me any documentation related to this topic ? I've searched into the Apple documentation without being able to find anything about that... And this is really confusing me.
- Second question : can someone please explain to me, why we are using (music) after super.play ? When I use auto-complete, the proposition is super.play(music: Music) and this is super confusing. It's like we are using the parameter name as a valide parameter here. Is this because, we intend to set the Music parameter first by creating an instance of Music ?-
- Third question : why are we talking about "special type" in the quote ? And what is a special type anyway ?
Thank you very much for reading all of this !
first: You can find docs here: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html#//apple_ref/doc/uid/TP40014097-CH10-ID158
second: You pass object which calls music into play method of superclass. Even if you override the method M in inherited class, you can access original implementation of method M in superclass.
Third question: Why did their pass object of type Music insted of [String]? This is OOP, you create classes for entities which want represent into program. Also, as you can see, class Music contains method prepared, which doesn't include Array type. And I think you will add some new helpful methods into Music class in the future.
Do not hesitate to ask questions.
As far as I understand, for passing data from a Class to a UIViewController, I used protocols & delegate, and it works like charm. What I did:
In Player class, I added:
public protocol PlayerDelegate {
func playerPlaybacksTimer(NSString: String)
}
public convenience init() {
player.addPeriodicTimeObserverForInterval(CMTimeMake(1, 100), queue: dispatch_get_main_queue()) {
[unowned self] time in
let timeString = String(format: "%02.2f", CMTimeGetSeconds(time))
self.delegate?.playerPlaybacksTimer(timeString)
}
}
and then I added the protocol & protocol method in UIViewController:
class ViewController: UIViewController, PlayerDelegate {
func playerPlaybacksTimer(currentTime: String) {
durationSlider.value = Float(currentTime)!
print(currentTime)
}
}
Until here, it works nicely and I can get the current time of the player. However, now, I am trying to do the reverse, so send data from ViewController to Class. (To make what I want to achieve clear, using above approach, I assigned the current playing time of Player to a UISlider and the slider's value is changing as the video goes on)
At this point, I want to make the user to be able to change the UISlider value and manipulate/change the current playing time of the video. As far as I understand, I should use seekToTime but I am completely lost on how to achieve that.
Should I set another protocol from the ViewController to the Class? What is the way to achieve that the UISlider's value is assigned to the current value?
At least, please guide me on the first step I should take to pass data from the VC to the Class? I assume once I get the slider's value in the Player class, I can try playing with the SeekToTime approach to understand and apply.
P.S: I am using the demo project of Player (which is a quiet straight forward library) that uses AVPlayer & AVFoundation
P.S 2: I asked another question on similar topic and opened a bounty here. Please also check there, for more detailed explanation on my issue (and possibly get a 50 points bounty).
My question is very similar to several others here but I just can't get it to work. I'm making an API call via a helper class that I wrote.
First I tried a standard function with a return value and the result was as expected. The background task completed after I tired to assign the result.
Now I'm using a closure and I can get the value back into my view controller but its still stuck in the closure, I have the same problem. I know I need to use GCD to get the assignment to happen in the main queue.
this is what I have in my view controller
var artists = [String]()
let api = APIController()
api.getArtistList("foo fighters") { (thelist) -> Void in
if let names = thelist {
dispatch_async(dispatch_get_main_queue()) {
artists = names
print("in the closure: \(artists)")
}
}
}
print ("method 1 results: \(artists)")
as the results are:
method 1 results: []
in the closure: [Foo Fighters & Brian May, UK Foo Fighters, John Fogerty with Foo Fighters, Foo Fighters, Foo Fighters feat. Norah Jones, Foo Fighters feat. Brian May, Foo Fighters vs. Beastie Boys]
I know why this is happening, I just don't know how to fix it :( The API calls need to be async, so what is the best practice for capturing these results? Based on what the user selects in the table view I'll be making subsequent api calls so its not like I can handle everything inside the closure
I completely agree with the #Craig proposal of the use of the GCD, but as your question involves the request of the API call every time you select a row, you can do the following:
Let's suppose you use the tableView:didSelectRowAtIndexPath: method to handle the selection, then you can do the following inside it:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// it is just a form to get the item
let selectedItem = items.objectAtIndex(indexPath.row) as String
api.getArtistList(selectedItem) { (thelist) -> Void in
if let names = thelist {
dispatch_async(dispatch_get_main_queue()) {
artists = names
}
}
}
}
And then you can observe the property and handle do you want inside it :
var artists: [String] = [] {
didSet {
self.tableView.reloadData() // or anything you need to handle.
}
}
It just another way to see it. I hope this help you.
The easy solution is to do whatever you're doing at your print(), inside the closure.
Since you're already dispatch_asyncing to the main queue (the main/GUI thread), you can complete any processing there. Push a new view controller, present some modal data, update your current view controller, etc.
Just make sure that you don't have multiple threads modifying/accessing your local/cached data that is being displayed. Especially if it's being used by UITableViewDelegate / UITableViewDataSource implementations, which will throw fits if you start getting wishy-washy or inconsistent with your return values.
As long as you can retrieve the data in the background, and the only processing that needs to occur on the main thread is an instance variable reassignment, or some kind of array appending, just do that on the main thread, using the data you retrieved on the back end. It's not heavy. If it is heavy, then you're going to need more sophisticated synchronization methods to protect your data.
Normally the pattern looks like:
dispatch_async(getBackgroundQueue(), {
var theData = getTheDataFromNetwork();
dispatch_async(dispatch_get_main_queue() {
self.data = theData // Update the instance variable of your ViewController
self.tableView.reloadData() // Or some other 'reload' method
});
})
So where you'd normally refresh a table view or notify your ViewController that the operation has completed (or that local data has been updated), you should continue your main-thread processing.
I am creating a game in which, depending on the number of 'swipes' chosen to do, (let's say 3), 3 different patterns show on the screen, one by one. I am working on developing the first pattern.
So I have this:
if (swipes.no_of_swipes) == 3 {
swipeArray = Array<UInt32>(count: 3, repeatedValue: 0)
for i in 0 ..< 3 {
swipeArray[i] = arc4random_uniform(84)}
}
As far as I am aware, this code creates an array with three UInts which can be accessed by doing swipeArray[0], swipeArray[1], and swipeArray[2]. My first question is how long will this swipeArray stay the same? Until the close the view? Should I have a 'refresh button' when the user loses - and if so, how would I make one?
Then I have a property observer. You will notice the for loop, which I am using to keep code concise. I understand that I could do something like x++ somewhere in here so that it will go through each one.
var playBegin: Bool = false{
didSet {
if playBegin == true {
println("\(playBegin)")
var swipes = Menu()
if (swipes.no_of_swipes) == 3 {
for i in 0 ..< 3 {
patternRoom.image = UIImage(named: "pattern\(swipeArray[x])")
//rest of code
}
}
}
The pattern image comes from a set of 84 images named like pattern7 and pattern56. My second question is, how could I code the for loop to go through each swipeArray[x].
Thank you in advance,
Will
how long will this swipeArray stay the same?
This is a bit too open ended. It’ll stay the same until you assign a new value to it, either from this same bit of code or a different part. Only you can know when that will be, by looking at your code.
Since you express an interest in keeping the code concise, here’s a couple of code tips.
You might think about writing your first snippet’s loop like this:
swipeArray = (0..<swipes.no_of_swipes).map { _ in
arc4random_uniform(84)
}
This combines creating a new array and populating the values. By the way, just in case you don’t realize, there’s no guarantee this array won’t contain the same value twice.
It’s also probably better to make swipeArray of type [Int] rather than [UInt32], and to convert the result of arc4random to an Int straight away:
Int(arc4random_uniform(84))
Otherwise the UInt32s will probably be a pain to work with.
For your second for loop, you can do this:
for i in swipeArray {
patternRoom.image = UIImage(named: "pattern\(i)")
// rest of code
}
When writing Swift, usually (but not always), when you find yourself using array[x] there’s a better more expressive way of doing it.