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).
Related
I was converting one of my swift project into SwiftUI. I need to convert delegtes and protocols to SwiftUI, is it allowed in SwiftUI? or any alternative methods are there? Please help me i'm so confused in SwiftUI.
I'm calling a delegate method from one of my class, then delegate method will be present in another ViewController.
//PresenterClass
protocol SplashPresenterDelegate {
func didFetchedSystemInfo(info: String)
}
class SplashPresenter: NSObject {
var delegate: SplashPresenterDelegate?
func getSystemInfo(){
self.delegate?.didFetchedSystemInfo(info: "ResponseString")
}
}
// Viewcontroller class
class myViewController: UIViewController {
.
.
.
}
extension myViewController: SplashPresenterDelegate{
func didFetchedSystemInfo(info: String){
print("delegate called")
}
}
Please help me to convert this code to SwiftUI
Typically, SwiftUI prefers the pattern of taking callbacks rather than protocol/delegates. For instance, each button in an ActionSheet has a closure that is executed when the button is tapped.
You shouldn't just convert your code over directly, though. SwiftUI, being declarative, uses different paradigms for a lot of things. For instance, you wouldn't have a didFetchInfo method, you would just assign it to a #Published variable.
Think about having "single sources of truth", where a single variable is always correct. For example, List simply takes an array and updates when the array changes, unlike UITableView where you provide the data through numberOfRows and cellForRowAt. I don't know enough about your specific project to give more detail, but those are things to think about.
Example: I have a SpeechSynthesizer class that needs to update something in my UIView when it’s done uttering a piece of text. Since the SpeechSynthesizer class conforms to protocol AVSpeechSynthesizerDelegate, it is the one that receives the didFinish signal when the uttering has been completed. The idea here is to keep the ViewController from having too many delegate methods and a long list of protocols to conform to. The workaround I found was to have the ViewController passed in as a SpeechSynthesizer initialization parameter. This way I get to access the ViewController connected to the UIView I want to update from inside the SpeechSynthesizer class. The thing I don’t like about it is that it looks kind of ugly to have the ViewController passed in as a parameter to every single class that needs to use it. So I wonder, which other way I could accomplish this.
I suppose another way to ask the question is: How can I make the function
private func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance)
return something to a ViewController since it's not "called" by it?
I added a reply on Quora. Copying it here:
After doing some research and testing on code of my own here are 2 solutions to this problem.
Solution 1: The Delegate Pattern:
Create a custom delegate protocol in the ViewController
protocol ViewControllerDelegate:class {
func getViewLayer() -> CALayer
}
The ViewController must conform to this newly created protocol and therefore implement all the functions defined by it, so somewhere in the class ViewController you add:
public func getViewLayer() -> CALayer {
return self.view.layer
}
Then on my custom class, ReadTextMachine, I added a variable of the ViewControllerDelegate type
private weak var viewControllerDelegate: ViewControllerDelegate?
The variable must be weak and protocol must be of type class in order to solve a “retain cycle” problem (since both the custom class and the ViewController will point to each other)
You’ll notice now that the function call inside the ViewController is already “callable” from the custom class, so in my ReadTextMachine I added:
let viewLayer = self.viewControllerDelegate?.getViewLayer()
self.cameraPreview = CameraPreview(session: self.camera.getSession(), container: viewLayer!)
self.cameraPreview?.addPreview()
In the above case, my CameraPreview (yes, a 3rd class in this example) simply adds a camera preview layer on the UIView. For that it needed access to the main View’s layer.
The above code still doesn’t work because our original viewController’s instance hasn’t been passed as reference anywhere in our code. For that we add the following function in ReadTextMachine:
public func setViewControllerDelegate(viewController: ViewController) { // call this from the ViewController so that ViewController can be accessed from here.
self.viewControllerDelegate = viewController
}
The above piece of code will have to be called from the ViewController, after we instantiate our custom class (ReadTextMachine), so that the viewControllerDelegate inside it points to the ViewController. So in our ViewController.swift:
operatingMode = ReadTextMachine()
operatingMode.setViewControllerDelegate(viewController: self)
Another example and explanation can be found in this video from LetsBuildThatApp. I derived my solution mostly from it.
My current app in development applying the above solution can be found here: agu3rra/World-Aloud
Solution 2: Notifications and Observers pattern
This one is a bit easier to understand and follow. The general idea is to have your custom class broadcast a message which triggers a function call on your ViewController since it has an observer setup, waiting to hear that message.
So to give an example, in the context I used it, I have a CameraCapture class which uses AVFoundation to capture a photo. The capture photo trigger cannot immediately return an image, since iOS has a set of steps to execute before actually generating an image. I wanted my ReadTextMachine to resume an activity after CameraCapture had a photo available. (To apply this in the context of the CustomClass triggers ViewController event is basically the same, since both are actual classes in an iOS app as well).
So the 1st thing I did was create a broadcast function since I would use it in many places in my app. I simply placed it in a Utilities.swift file in the Xcode project.
public func broadcastNotification(name: String) {
let notification = Notification.Name(rawValue: name)
NotificationCenter.default.post(name: notification, object: nil)
}
The above function takes a string, which must be a unique notification identifier, and broadcasts it thru NotificationCenter.
In my CameraCapture class, I added a static constant to reference the unique identifier of the message:
static let NOTIFY_PHOTO_CAPTURED = "agu3rra.worldAloud.photo.captured"
For those who know AVFoundation, a photo is available when event didFinishProcessingPhoto gets executed, so at the end of that I added:
broadcastNotification(name: CameraCapture.NOTIFY_PHOTO_CAPTURED)
The above is a call to my previously defined utility function.
For my ReadTextMachine class to be able to catch that notification, I added the following on its init() and deinit routines:
override init() {
super.init()
// Setup event observers
let notification1 = Notification.Name(rawValue: CameraCapture.NOTIFY_PHOTO_CAPTURED)
NotificationCenter.default.addObserver(self,
selector: #selector(self.processingDoneTakingPhoto),
name: notification1,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self) // cleanup observer once instance no longer exists
}
Removing the observer is important at deinit so that when your object is deallocated from memory, the observer isn’t left lingering around. The above configured observer triggers a function call inside ReadTextMachine:
#IBAction private func processingDoneTakingPhoto() {
// does my stuff
}
That’s it! Again, the entire Xcode project can be downloaded from my project’s Git repository: agu3rra/World-Aloud
Hope this can be of use to others.
Cheers!
Having my first crack at POP. In this case I want to decorate some UIViewControllers so that any that they automatically raise a 'Page viewed' analytics event.
So I created a protocol, and and extension for that protocol:
protocol ReportPageViewedEvent {
func reportPageViewed()
var pageName : String? { get set }
}
extension ReportPageViewedEvent where Self: UIViewController
{
func reportPageViewed()
{
guard let pageName = self.pageName else
{
fatalError("UIViewController implements ReportPageViewEvent protocol but did not set pageName property")
}
let eventBusiness = EventBusiness.sharedInstance
eventBusiness.logUserViewedPage(pageName)
}
}
This works as I want, if I decorate a UIViewController with ReportPageViewedEvent like this:
class HomeView: UIViewController, ReportPageViewedEvent {
I get a compiler error unless I set 'pageName' which is exactly what I want.
Where I am getting unstuck is where and how to call the actual reportPageViewed() method. I really want it to be called from viewDidLoad which means I either have to modify every 'viewDidLoad' in every controller that uses it, or subclass and call the method in the super class which defies the point of using POP in the first place.
Is there a nice way to achieve this. I can't find an example like this in any tutorial/blog.
Basically, there is always some behaviour shared by all the screens of your app. So it is appropriate to create a class called (for example) BaseViewController so all the other view controllers will inherit from it.
In BaseViewController's viewDidLoad you can call the reportPageViewed() method.
However, this approach makes the Protocol Oriented Programming not needed. Protocols are useful when you need to assign some same behaviour to objects that have nothing in common (which is not the case for app screens).
Can you please advise me how to initialize a Singleton in an iOS storyboard? My understanding so far is that the standard pattern looks like this inside the class that we want to be a singleton and this code works:
class var sharedInstance: LibraryAPI {
struct Singleton {
static let instance = LibraryAPI()
}
return Singleton.instance
}
The same pattern does not work for MusicPlayer:
I'm making a custom MusicPlayer object that relies on AVPlayer to play audio. I would like this MusicPlayer to exist as soon as the App is activated. At present the MusicPlayer is create in the Storyboard to allow me to use IBOutlets and IBActions to connect the buttons and labels to the MusicPlayer. The challenge is the MusicPlayer gets deallocated when I navigate back up the viewController stack. As shown in the figure below.
Which methods can I override to see when the Storyboard creates and destroys the MusicPlayer? So far you can see that override init() is called but deinit is not. I'm not having any luck finding the answer in the docs or online yet.
It's possible there is a better way to design this app. I think that Singleton makes sense because there should only ever be one MusicPlayer. Only it's state will change throughout the life of the app.
Thank you for your advice.
class var sharedInstance: LibraryAPI {
struct Singleton {
static let instance = LibraryAPI()
}
return Singleton.instance
}
This seems very over-complicated. You can generally just use:
final class MusicPlayer {
let sharedInstance = MusicPlayer()
init() {}
}
Things do get a bit more complex if Something is not final, but usually it can be for singletons.
At present the MusicPlayer is create in the Storyboard to allow me to use IBOutlets and IBActions to connect the buttons and labels to the MusicPlayer.
This won't work. See rdar:25450179 for my version of the problem. The latest explanation from Apple is that this probably won't be fixed. It would require too much redesign of Storyboards. They may change their mind, but it's a big change.
The closest you can come is to expose the singleton through your class.
class MyClass: UIViewController {
var musicPlayer: MusicPlayer { return MusicPlayer.sharedInstance() }
}
But this probably isn't going to do what you want, either. Generally, you need to tie your IBOutlets directly to the view controller. The view controller can then proxy to other objects as needed.
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.)