Playing Vimeo videos on the web displays close captioning controls like this.
How do I display similar controls on iOS with the Vimeo/PlayerKit?
I've looked high and low but found no documentation on the player kit at all.
Since no one else has jumped up with an answer, this is what I've done. Documentation for text tracks in Vimeo/PlayerKit is found in the files Player.swift and RegularPlayer.swift.
From that I constructed a UIButton subclass, CloseCaptionButton, that I display on the video as it plays and allows users to switch tracks.
// A UIAlertAction subclass to pass language code to selected action.
class TaggedAlertAction: UIAlertAction {
var languageCode: String?
public convenience init(title: String?, languageCode: String?, style: UIAlertAction.Style, handler: ((UIAlertAction) -> Void)? = nil) {
self.init(title: title, style: style, handler: handler)
self.languageCode = languageCode
}
}
class CloseCaptionButton: UIButton {
var languages: [PlayerKit.TextTrackMetadata]?
func handleTapFrom(vc: UIViewController, videoPlayer: RegularPlayer) {
guard let languages = languages else { return print("NIL LANGUAGES, Couldn't open language picker") }
let alert = UIAlertController(title: "Pick Captions Language", message: nil, preferredStyle: .actionSheet)
// Create an action item in alert for each language.
languages.forEach({ (textTrack) in
let languageCode = textTrack.language
let name = textTrack.title
alert.addAction(TaggedAlertAction(title: name, languageCode: languageCode, style: .default) { action in
let identifier = (action as! TaggedAlertAction).languageCode
self.selectTrackFor(identifier, videoPlayer: videoPlayer)
})
})
// Last action item allows user to turn off captions (or cancel).
alert.addAction(UIAlertAction(title: "Turn Off Captions", style: .default) { _ in
videoPlayer.select(nil)
})
alert.popoverPresentationController?.sourceView = vc.view
vc.present(alert, animated: true)
}
func selectTrackFor(_ languageCode: String?, videoPlayer: RegularPlayer) {
guard let languageCode = languageCode else { return print("NIL IDENTIFIER") }
let matches = videoPlayer.availableTextTracks.filter({ (track) -> Bool in
return track.language == languageCode
})
guard let match = matches.first else {
return print("No match for language: \(languageCode)")
}
videoPlayer.select(match)
}
// Configure with player.availableTextTracks
func configure(_ tracks: [PlayerKit.TextTrackMetadata]) {
languages = Languages(trackMetadata: tracks)
}
}
// How to use
closeCaptionButton.configure(videoPlayer.availableTextTracks)
#IBAction func ccButtonTapped(_ sender: Any) {
closeCaptionButton.handleTapFrom(vc: self, videoPlayer: videoPlayer)
}
Please enjoy!
Related
I have created a class to perform a network request and parse the data using Combine. I'm not entirely certain the code is correct, but it's working as of now (still learning the basics of Swift and basic networking tasks). My Widget has the correct data and is works until the data becomes nil. Unsure how to check if the data from my first publisher in my SwiftUI View is nil, the data seems to be valid even when there's no games showing.
My SwiftUI View
struct SimpleEntry: TimelineEntry {
let date: Date
public var model: CombineData?
let configuration: ConfigurationIntent
}
struct Some_WidgetEntryView : View {
var entry: Provider.Entry
#Environment(\.widgetFamily) var widgetFamily
var body: some View {
VStack (spacing: 0){
if entry.model?.schedule?.dates.first?.games == nil {
Text("No games Scheduled")
} else {
Text("Game is scheduled")
}
}
}
}
Combine
import Foundation
import WidgetKit
import Combine
// MARK: - Combine Attempt
class CombineData {
var schedule: Schedule?
var live: Live?
private var cancellables = Set<AnyCancellable>()
func fetchSchedule(_ teamID: Int, _ completion: #escaping (Live) -> Void) {
let url = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule?teamId=\(teamID)")!
let publisher = URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: Schedule.self, decoder: JSONDecoder())
//.catch { _ in Empty<Schedule, Error>() }
//.replaceError(with: Schedule(dates: []))
let publisher2 = publisher
.flatMap {
return self.fetchLiveFeed($0.dates.first?.games.first?.link ?? "")
}
Publishers.Zip(publisher, publisher2)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in
}, receiveValue: { schedule, live in
self.schedule = schedule
self.live = live
completion(self.live!)
WidgetCenter.shared.reloadTimelines(ofKind: "NHL_Widget")
}).store(in: &cancellables)
}
func fetchLiveFeed(_ link: String) -> AnyPublisher<Live, Error /*Never if .catch error */> {
let url = URL(string: "https://statsapi.web.nhl.com\(link)")!
return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: Live.self, decoder: JSONDecoder())
//.catch { _ in Empty<Live, Never>() }
.eraseToAnyPublisher()
}
}
Like I said in the comments, it's likely that the decode(type: Live.self, decoder: JSONDecoder()) returns an error because the URL that you're fetching from when link is nil doesn't return anything that can be decoded as Live.self.
So you need to handle that case somehow. For example, you can handle this by making the Live variable an optional, and returning nil when link is empty (or nil).
This is just to set you in the right direction - you'll need to work out the exact code yourself.
let publisher2 = publisher1
.flatMap {
self.fetchLiveFeed($0.dates.first?.games.first?.link ?? "")
.map { $0 as Live? } // convert to an optional
.replaceError(with: nil)
}
Then in the sink, handle the nil:
.sink(receiveCompletion: {_ in }, receiveValue:
{ schedule, live in
if let live = live {
// normal treatment
self.schedule = schedule
self.live = live
//.. etc
} else {
// set a placeholder
}
})
SwiftUI and WidgetKit work differently. I needed to fetch data in getTimeline for my IntentTimelineProvider then add a completion handler for my TimelineEntry. Heavily modified my Combine data model. All credit goes to #EmilioPelaez for pointing me in the right direction, answer here.
I am using custom SwiftUI view from main target for sharing document from ShareViewController of share extension. Using Navigation link and sharing the document after navigating through three views. document is uploaded without any problem, but I don't know how to close the views after upload is done.
This is how navigation looks like
ShareViewController(SLComposeServiceViewController) -> PropertyListView -> UnitListView -> UploadView
and didPost looks like this
override func didSelectPost() {
print("In Did Post")
if let item = self.extensionContext?.inputItems[0] as? NSExtensionItem{
print("Item \(item)")
print(item.attachments)
print(item.attachments![0])
let itemProvider = item.attachments![0]
if itemProvider.hasItemConformingToTypeIdentifier("com.adobe.pdf"){
itemProvider.loadItem(forTypeIdentifier: "com.adobe.pdf", options: nil) { (item, error) in
if error != nil{
print(error!.localizedDescription)
}else{
if let url = item as? URL{
print(url)
DispatchQueue.main.async{
//saving to user defaults
let dict: [String : Any] = ["dcument" : url.absoluteString, "name" : self.contentText.isEmpty ? url.lastPathComponent : self.contentText!]
let savedata = UserDefaults.init(suiteName:"group.in.pixbit.hijricalendar")
savedata?.set(dict, forKey: "sharedDocument")
savedata?.synchronize()
//loading swiftui view
let swiftuiView = NavigationView{PropertyListView()}
let vc = UIHostingController(rootView: swiftuiView)
let newView = vc
self.view.window?.rootViewController = newView
self.view.window?.makeKeyAndVisible()
}
}
}
}
}
}
}
You need to call completeRequest(returningItems:completionHandler:) on the extensionContext of your view controller.
Here is a code snippet from one of my apps:
override func didSelectPost() {
let artifact = Artifact(title: self.contentText,
author: self.metaAuthor,
url: url?.absoluteString ?? metaUrl ?? "",
imageUrl: metaImage,
siteName: self.siteName,
dateAdded: Date(),
excerpt: metaDescription,
notes: "",
tags: nil)
self.artifactRepository?.addArtifact(artifact)
self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
}
I have added some annotationViews at Map with init method (initialised by there id). Now I want to update specific id annotation view on click button from navigation bar.
Suppose I have added 5 annotation with ids (1, 2, 3, 4, and 5)
Added from VC:
let annotation = MapPinAnnotation(title: storeItem.name!, location: CLLocationCoordinate2DMake(Double(lat), Double(long)), id: storeItem.storeId!)
self.mapview.addAnnotation(annotation)
Initialised AnnotationView:
class MapPinAnnotation: NSObject, MKAnnotation {
var title:String?
var id:String?
private(set) var coordinate = CLLocationCoordinate2D()
init(title newTitle: String, location: CLLocationCoordinate2D, id: String) {
super.init()
self.title = newTitle
self.coordinate = location
self.id = id
}
}
ViewFor annotation method:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if (annotation is MKUserLocation) {
return nil
}
if (annotation is MapPinAnnotation) {
let pinLocation = annotation as? MapPinAnnotation
// Try to dequeue an existing pin view first.
var annotationView: MKAnnotationView? = mapView.dequeueReusableAnnotationView(withIdentifier: "MapPinAnnotationView")
if annotationView == nil {
annotationView?.image = UIImage(named: Constants.Assets.PinGreen)
}
else {
annotationView?.annotation = annotation
}
return annotationView
}
return nil
}
Now I want to change image of annotation view(id 4) on click button from navigation bar.
How can I update? Please help.
Thanks in advance.
You can get specific MKAnnotationView with view(for: ) method. Try the following code:
func clickButton() {
for annotation in self.mapView.annotations {
if annotation.id == 4 {
let annotationView = self.mapView.view(for: annotation)
annotationView?.image = UIImage(named: "Image name here")
}
}
}
So I have an image view titled cashOrCredit and have am trying to set it's image programatically but somehow not able to.
First I set the image like this
cell.cashOrCredit.image = UIImage(named: "cash1.png")
and I get an error saying a separator of "," is needed.
Then I tried it this way
var cashImage: UIImage?
cashImage = "cash1.png"
cell.cashOrCredit.image = cashImage
But I get a THREAD 1 EXC BAD INSTRUCTION error.
I can't seem to understand what is going wrong ?
Here is the error
Updated for Swift 3:
use below simple code, to set the image to UIImageView;
class YourViewControllerName: UIViewController {
var mYourImageViewOutlet: UIImageView?
func addImageToUIImageView{
var yourImage: UIImage = UIImage(named: "Birthday_logo")!
mYourImageViewOutlet.image = yourImage
} // call this function where you want to set image.
}
Note: "Birthday_logo" type of image must be present in your Assets.xcassets of your project.
I attached the screenshot if you want any help please refer it.
****// used anywhere you want to add an image to UIImageView. [Here I used one function & in that function, I write a code to set image to UIImageView]****
Enjoy..!
Try this:
cell.cashOrCredit.image = UIImage(named: "cash1")
and check "cash1.png" image is available in Assets.xcassets or not.
If you get solution, then give upvote to my answer.
Delete ".png":
cell.cashOrCredit.image = UIImage(named: "cash1")
You can also set it all programmatically:
let cellImage = UIImageView(frame: CGRect(x: X, y: Y, width: WIDTH, height: HEIGHT))
cellImage.image = UIImage(named: "cash1")
cell.addSubview(cellImage)
Take Outlet of ImageView
#IBOutlet weak var imgProfile: UIImageView!
Go through the following code which contains will be helpful you to pick image or capture image from the camera.
func choosePicker() {
let alertController = UIAlertController(title: "Select Option", message: nil, preferredStyle: (IS_IPAD ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet))
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: { action -> Void in
})
let gallery = UIAlertAction(title: "From Gallery", style: UIAlertActionStyle.default
, handler: { action -> Void in
self.openPicker(isCamera: false)
})
let camera = UIAlertAction(title: "Take Photo", style: UIAlertActionStyle.default
, handler: { action -> Void in
self.openPicker(isCamera: true)
})
alertController.addAction(gallery)
alertController.addAction(camera)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
func openPicker(isCamera : Bool) {
let picker:UIImagePickerController?=UIImagePickerController()
if isCamera {
picker!.sourceType = UIImagePickerControllerSourceType.camera
} else {
picker!.sourceType = UIImagePickerControllerSourceType.photoLibrary
picker!.allowsEditing = true
}
picker?.delegate = self
if UIDevice.current.userInterfaceIdiom == .phone {
self.present(picker!, animated: true, completion: nil)
}
else {
picker!.modalPresentationStyle = .popover
present(picker!, animated: true, completion: nil)//4
picker!.popoverPresentationController?.sourceView = imgProfile
picker!.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
}
}
// MARK: - UIImagePickerControllerDelegate Methods
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
self.imgProfile.image = image
}
picker.dismiss(animated: true, completion: nil);
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
Call choosePicker method wherever you want to call.
Check if cashOrCredit is uiimageview.
Secondly,
cashImage = UIImage(named: "cash1.png")
If that doesnot work, try
cell?. cashOrCredit.image
check if cell is nil?
In the context of Swift code, EXC_BAD_INSTRUCTION usually means you’ve hit a compiler trap, that is, an undefined instruction inserted into the code by the compiler because of a bug detected at runtime. The most common cause of these are:
failure to unwrap an optional — This can be a forced unwrap (!) or an implicit unwrap (accessing an implicitly unwrapped optional that’s nil).
array out of bounds
a failed forced cast (as!), either because the value was a nil optional or because the value was of the wrong type
I have implemented alert window with two text fields. And I want to perform some validation: if one of text fields is blank (user did not input any value), application should not allow user to press the "Done" button.
I don't want to show any alerts, just want to not allow user to save blank data.
I have created the function listed below. Added two guards with return. But in this case if user did not input anything, alert just closes and nothing is saved. I don't want alert window to be closed.
How can it be done if it can be? Have not found the answer. I have checked this question, but looks like it's not applicable for me. Appreciate your help!
private func addTable() {
let alert = UIAlertController(title: NSLocalizedString("inputTableParams", comment: ""), message: nil, preferredStyle: .alert)
alert.addTextField(configurationHandler: configureTableNameTextField)
alert.addTextField(configurationHandler: configureTableCapacityTextField)
alert.textFields?[0].autocapitalizationType = .sentences
alert.textFields?[1].autocapitalizationType = .sentences
alert.addAction(UIAlertAction(title: NSLocalizedString("alertCancel", comment: ""), style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("alertDone", comment: ""), style: .default, handler: { (UIAlertAction) in
guard self.tableNameTextField.text != "" else {return}
guard self.tableCapacityTextField.text != "" else {return}
let newTable = Table(tableName: self.tableNameTextField.text!, tableCapacity: Int(self.tableCapacityTextField.text!)!)
let result = try? TablesTable.getOrCreateTable(table: newTable)
if result != nil {
self.updateTableView()
}
}))
self.present(alert, animated: true, completion: {})
}
It looks like it is not possible to do exactly what I want, so I have implemented another alert window with error.
//MARK: Functions
//Functions for Alert window for adding table
private func configureTableNameTextField (textField: UITextField!) {
textField.placeholder = NSLocalizedString("tableName", comment: "")
textField.keyboardType = .default
tableNameTextField = textField
}
private func configureTableCapacityTextField (textField: UITextField!) {
textField.placeholder = NSLocalizedString("tableCapacity", comment: "")
textField.keyboardType = .numberPad
tableCapacityTextField = textField
}
private func showAlertParamsNotFilledProperly() {
let alertNoCanDo = UIAlertController(title: NSLocalizedString("alertNoCanDo", comment: ""), message: NSLocalizedString("paramsNotFilledProperly", comment: ""), preferredStyle: .alert)
alertNoCanDo.addAction(UIAlertAction(title: NSLocalizedString("alertDone", comment: ""), style: .cancel, handler: nil))
self.present(alertNoCanDo, animated: true, completion: {})
}
private func showAlertUnableToSave() {
let alertNoCanDo = UIAlertController(title: NSLocalizedString("alertUnableToSaveData", comment: ""), message: NSLocalizedString("checkInputParameters", comment: ""), preferredStyle: .alert)
alertNoCanDo.addAction(UIAlertAction(title: NSLocalizedString("alertDone", comment: ""), style: .cancel, handler: nil))
self.present(alertNoCanDo, animated: true, completion: {})
}
//Functions for managing tables
private func addTable() {
let alert = UIAlertController(title: NSLocalizedString("inputTableParams", comment: ""), message: nil, preferredStyle: .alert)
alert.addTextField(configurationHandler: configureTableNameTextField)
alert.addTextField(configurationHandler: configureTableCapacityTextField)
alert.textFields?[0].autocapitalizationType = .sentences
alert.textFields?[1].autocapitalizationType = .sentences
alert.addAction(UIAlertAction(title: NSLocalizedString("alertCancel", comment: ""), style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: NSLocalizedString("alertDone", comment: ""), style: .default, handler: { (UIAlertAction) in
if self.tableNameTextField.text == "" || self.tableCapacityTextField.text == "" {
self.showAlertParamsNotFilledProperly()
return
}
if let number = NumberFormatter().number(from: self.tableCapacityTextField.text!) {
let capacity = Int(number)
let newTable = Table(tableName: self.tableNameTextField.text!, tableCapacity: capacity)
let result = try? TablesTable.getOrCreateTable(table: newTable)
if result != nil {
self.updateTableView()
}
} else {
self.showAlertParamsNotFilledProperly()
return
}
}))
self.present(alert, animated: true, completion: {})
}