I am writing a macOS application with Swift using story boards. I have a NSTableView which contains files that I want the user to be able to preview via QuickLook.
I seemingly have everything in place and my code looks very similar to what has been described here: QuickLook consumer as a delegate from an NSViewController, but I keep getting the error
-[QLPreviewPanel setDataSource:] called while the panel has no controller - Fix this or this will raise soon.
See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.
I've been trying to adapt the solution of above post to my situation with Swift and story boards.
The main pieces are:
import Quartz
class ViewController: NSViewController, QLPreviewPanelDataSource, QLPreviewPanelDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let windowNextResponder = self.view.window?.nextResponder
self.view.window?.nextResponder = self
self.nextResponder = windowNextResponder
}
// *** Quicklook stuff ***
#IBAction func quickLookButtonAction(_ sender: Any) {
guard qlPanel != nil else {
return
}
if qlPanel!.currentController == nil {
print ("No controller")
//qlPanel!.windowController = self.view.window?.windowController
// qlPanel!.updateController()
} else {
print (qlPanel!.currentController)
}
qlPanel!.delegate = self
qlPanel!.dataSource = self
qlPanel!.makeKeyAndOrderFront(self)
}
func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
return CSVarrayController.selectedObjects.count
}
func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
let file = CSVarrayController.selectedObjects[index] as! CSVfile
return file.url as NSURL
}
override func acceptsPreviewPanelControl(_ panel: QLPreviewPanel!) -> Bool {
return true
}
override func beginPreviewPanelControl(_ panel: QLPreviewPanel!) {
panel.dataSource = self
panel.delegate = self
}
override func endPreviewPanelControl(_ panel: QLPreviewPanel!) {
panel.dataSource = nil
panel.delegate = nil
}
}
With or without messing with the responder chain I get the error.
The delegate functions all get called as expected as well.
Remove
qlPanel!.delegate = self
qlPanel!.dataSource = self
in quickLookButtonAction, the viewcontroller isn't in control yet. Wait for beginPreviewPanelControl.
From the documentation for currentController:
You should never change the preview panel’s state (its delegate, datasource, and so on) if you are not controlling it.
From comments in QLPreviewPanel.h for -beginPreviewPanelControl::
Sent to the object taking control of the Preview Panel.
The receiver should setup the preview panel (data source, delegate, binding, etc.) here.
Related
I've successfully set my first view controller to STPAddCardViewController. I now need to get the user information in the STPPaymentCardTextField. Problem is, I'm used to using the storyboard to make outlets. How do I detect the STPPaymentCardTextField programmatically?
I've tried:
class ViewController: STPAddCardViewController, STPPaymentCardTextFieldDelegate {
let paymentCardTextField = STPPaymentCardTextField()
func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) {
print(paymentCardTextField.cardNumber)
//ERROR: printing nil in the console
}
}
But I'm getting nil as an output. Any help?
You should use either STPAddCardViewController, or STPPaymentCardTextField, not both. The SDK's ViewControllers are not designed to be extended. The intended use is:
class MyVC : STPAddCardViewControllerDelegate {
override func viewDidLoad() {
…
let addCardView = STPAddCardViewController()
addCardView.delegate = self
// Start the addCardView
self.navigationController.pushViewController(addCardView, animated: true)
}
…
func addCardViewController(_ addCardViewController: STPAddCardViewController, didCreatePaymentMethod paymentMethod: STPPaymentMethod, completion: #escaping STPErrorBlock) {
// TODO: do something with paymentMethod
// Always call completion() to dismiss the view
completion()
}
func addCardViewControllerDidCancel(_ addCardViewController: STPAddCardViewController) {
// TODO: handle cancel
}
}
But rather than my partial example I'd recommend reading these docs and trying out this example iOS code. Best wishes!
I am integrating TouchBar support to my App. I used the how to from Rey Wenderlich and implemented everything as follows:
If self.touchBarArraygot filled the makeTouchBar() Method returns the NSTouchBar object. If I print out some tests the identifiers object is filled and works.
What not work is that the makeItemForIdentifier method not get triggered. So the items do not get created and the TouchBar is still empty.
Strange behavior: If I add print(touchBar) and a Breakpoint before returning the NSTouchBar object it works and the TouchBar get presented as it should (also the makeItemForIdentifier function gets triggered). Even if it disappears after some seconds... also strange.
#available(OSX 10.12.2, *)
extension ViewController: NSTouchBarDelegate {
override func makeTouchBar() -> NSTouchBar? {
if(self.touchBarArray.count != 0) {
let touchBar = NSTouchBar()
touchBar.delegate = self
touchBar.customizationIdentifier = NSTouchBarCustomizationIdentifier("com.TaskControl.ViewController.WorkspaceBar")
var identifiers: [NSTouchBarItemIdentifier] = []
for (workspaceId, _) in self.touchBarArray {
identifiers.append(NSTouchBarItemIdentifier("com.TaskControl.ViewController.WorkspaceBar.\(workspaceId)"))
}
touchBar.defaultItemIdentifiers = identifiers
touchBar.customizationAllowedItemIdentifiers = identifiers
return touchBar
}
return nil
}
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
if(self.touchBarArray.count != 0) {
for (workspaceId, data) in self.touchBarArray {
if(identifier == NSTouchBarItemIdentifier("com.TaskControl.ViewController.WorkspaceBar.\(workspaceId)")) {
let saveItem = NSCustomTouchBarItem(identifier: identifier)
let button = NSButton(title: data["name"] as! String, target: self, action: #selector(self.touchBarPressed))
button.bezelColor = NSColor(red:0.35, green:0.61, blue:0.35, alpha:1.00)
saveItem.view = button
return saveItem
}
}
}
return nil
}
}
self.view.window?.makeFirstResponder(self) in viewDidLoad() did solve the problem.
first of all thank you for reading my lines.
For an idea I'm currently trying to dive into the Swift world (I only have very basic programming knowledge - no Objective C knowledge
).
I tried to set up the following lines to create a very basic app-to-app sample in Sinch. After my code I let you know what the issues are.
import UIKit
import Sinch
var appKey = "APP_KEY_FROM_MY_ACCOUNT"
var hostname = "clientapi.sinch.com"
var secret = "SECRET_FROM_MY_ACCOUNT"
class CViewController: UIViewController, SINCallClientDelegate, SINCallDelegate, SINClientDelegate {
var client: SINClient?
var call: SINCall?
var audio: SINAudioController?
//Text field in the main storyboard
#IBOutlet weak var userNameSepp: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.initSinchClient()
}
//initialize and start the client as a fixed "userA"
func initSinchClient() {
client = Sinch.client(withApplicationKey: appKey, applicationSecret: secret, environmentHost: hostname, userId: "userB")
client?.call().delegate = self
client?.delegate = self
client?.startListeningOnActiveConnection()
client?.setSupportCalling(true)
client?.start()
}
//Did the Client start?
func clientDidStart(_ client: SINClient!) {
print("Hello")
}
//Did the Client fail?
func clientDidFail(_ client: SINClient!, error: Error!) {
print("Good Bye")
}
//Call Button in the main.storyboard ... if call==nil do the call ... else hangup and set call to nil
//the background color changes are my "debugging" :D
#IBAction func callSepp(_ sender: Any) {
if call == nil{
call = client?.call()?.callUser(withId: userNameSepp.text)
//for testing I change to callPhoneNumber("+46000000000").
// the phone call progresses (but I hear nothing),
// the phonecall gets established (but I hear nothing)
// and the phonecall gets ended (but of course I hear nothing)
self.view.backgroundColor = UIColor.red
call?.delegate = self
audio = client?.audioController()
}
else{
call?.hangup()
self.view.backgroundColor = UIColor.blue
call = nil
}
}
func callDidProgress(_ call: SINCall?) {
self.view.backgroundColor = UIColor.green
client?.audioController().startPlayingSoundFile("/LONG_PATH/ringback.wav", loop: true)
print("Call in Progress")
}
//I know that this works but I don't hear anything
func callDidEstablish(_ call: SINCall!) {
client?.audioController().stopPlayingSoundFile()
print("Call did Establish")
}
func callDidEnd(_ call: SINCall!) {
print("Call did end")
}
// this works fine
#IBAction func hangUpSepp(_ sender: Any) {
call?.hangup()
self.view.backgroundColor = UIColor.red
call = nil
}
// i work in a "sub view controller" - so i navigate here back to the main view controller
#IBAction func goBackMain(_ sender: Any) {
call?.hangup()
dismiss(animated: true, completion: nil)
client?.stopListeningOnActiveConnection()
client?.terminateGracefully()
client = nil
}
}
So I can call my private phone number or if I change to callUser I can call another app but I don't hear anything. What do I miss? It must have to do with the SINAudioController and the client's method audioController() but I don't know what I'm doing wrong. Thank you for your help.
Is it possible to use a UISegmentedControl with 3 segments as if it was a three-way UISwitch? I tried to use one as a currency selector in the settings section of my app with no luck, it keeps reseting to the first segment when I switch views and that creates a big mess.
I proceeded like that:
IBAction func currencySelection(_ sender: Any) {
switch segmentedControl.selectedSegmentIndex {
case 0:
WalletViewController.currencyUSD = true
WalletViewController.currencyEUR = false
WalletViewController.currencyGBP = false
MainViewController().refreshPrices()
print(0)
case 1:
WalletViewController.currencyUSD = false
WalletViewController.currencyEUR = true
WalletViewController.currencyGBP = false
MainViewController().refreshPrices()
print(1)
case 2:
WalletViewController.currencyUSD = false
WalletViewController.currencyEUR = false
WalletViewController.currencyGBP = true
MainViewController().refreshPrices()
print(2)
default:
break
}
}
The UISegmentedControl is implemented in the
SettingsViewController of the app to choose between currencies to
display in the MainViewController.
(Taken from a comment in #pacification's answer.)
This was the missing piece I was looking for. It provides a lot of context.
TL;DR;
Yes, you can use a three segment UISegmentedControl as a three-way switch. The only real requirement is that you can have only one value or state selected.
But I wasn't grasping why your code referred to two view controllers and some of switching views resulting in resetting the segment. One very good way to do what you want is to:
Have MainViewController present SettingsViewController. Presenting it modally means the user is only doing one thing at a time. When they are making setting changes, you do not want them adding new currency values.
Create a delegate protocol in SettingsViewController and make MainViewController conform to it. This tightly-couples changes made to the settings to the view controller interested in what those changes are.
Here's a template for what I'm talking about:
SettingsViewController:
protocol SettingsVCDelegate {
func currencyChanged(sender: SettingsViewController)
}
class SettingsViewController : UIViewController {
var delegate:SettingsVCDelegate! = nil
var currency:Int = 0
#IBAction func valueChanged(_ sender: UISegmentedControl) {
currency = sender.selectSegmentIndex
delegate.currencyChanged(sender:self)
}
}
MainViewController:
class MainViewController: UIViewController, SettingsVCDelegate {
var currency:Int = 0
let settingsVC = SettingsViewController()
override func viewDidLoad() {
super.viewDidLoad()
settingsVC.delegate = self
}
func presentSettings() {
present(settingsVC, animated: true, completion: nil)
}
func currencyChanged(sender:SettingsViewController) {
currency = sender.currency
}
}
You can also create an enum of type Int to make your code more readable, naming each value as currencyUSD, currencyEUR, and currencyGBP. I'll leave that to you as a learning exercise.
it keeps reseting to the first segment when I switch views
yes, it is. to avoid this situation you should set the correct switch value to the segmentedControl.selectedSegmentIndex every time when you load your view with UISegmentedControl.
UPD
Ok, the behavior of MainViewController can be similar to this:
final class MainViewController: UIViewController {
private var savedValue = 0
override func viewDidLoad() {
super.viewDidLoad()
}
func openSettingsController() {
let viewController = SettingsController.instantiate() // simplify code a bit. use the full controller initialization
viewController.configure(value: savedValue, onValueChanged: { [unowned self] value in
self.savedValue = value
})
navigationController?.pushViewController(viewController, animated: true)
}
}
And the SettingsViewController:
final class SettingsViewController: UIViewController {
#IBOutlet weak var segmentedControl: UISegmentedControl!
private var value: Int = 0
var onValueChanged: ((Int) -> Void)?
func configure(value: Int, onValueChanged: #escaping ((Int) -> Void)) {
self.value = value
self.onValueChanged = onValueChanged
}
override func viewDidLoad() {
super.viewDidLoad()
segmentedControl.selectedSegmentIndex = value
}
#IBAction func valueChanged(_ sender: UISegmentedControl) {
onValueChanged?(sender.selectedSegmentIndex)
}
}
The main idea that you should keep your selected value if you moving from SettingsViewController. For this thing you can create closure
var onValueChanged: ((Int) -> Void)?
that pass back to MainViewController the selected UISegmentedControl value. And in future when you will open the SettingsViewController again you just configure() this value and set it to UI.
I've been debugging my code and found that my manager was deinitialised (that was cause of my bug - not calling delegate methods).
What's strange, that during debugging process I've used "po" command after setting the manager's delegate (weak) and it prevented it from being deinitialised (delegate methods were called).
Why is that? Is it proper behaviour?
Xcode 8.3, swift 3.1
EDIT:
//a tap starts everything :)
#IBAction func shareButtonPressed(_ sender: Any) {
let requestManager = FacebookPostRouteRequest() //bug fixed by changing to instance variable
requestManager.delegate = self
requestManager.showShareBadgeDialog(self.badge!, onViewController: self)
}
//in FacebookPostRouteRequest
final weak var delegate: FacebookPostRouteRequestDelegate?
func showShareBadgeDialog(_ badge: Badge, onViewController viewController: UIViewController) {
let dialog = self.initDialog(onViewController: viewController)
guard let imageURL = badge.imageURL else {
self.delegate?.facebookPostRouteRequest(self, didCompleteWithResult: false)
return
}
dialog.shareContent = self.generateImageShareContent(imageURL)
self.show(dialog)
}
private func show(_ dialog: FBSDKShareDialog) {
OperationQueue.main.addOperation {
dialog.delegate = self //when printed out dialog.delegate delegate methods were called! Deinit of FacebookPostRouteRequest is not called.
let showResult = dialog.show()
...
}
}
extension FacebookPostRouteRequest: FBSDKSharingDelegate {
func sharer(_ sharer: FBSDKSharing!, didCompleteWithResults results: [AnyHashable : Any]!) {
...
}
//other delegate methods implemented as well
}
Your problem is here:
#IBAction func shareButtonPressed(_ sender: Any) {
let requestManager = FacebookPostRouteRequest()
requestManager.delegate = self
requestManager.showShareBadgeDialog(self.badge!, onViewController: self)
}
After the last line, the requestManager object will be disposed because it's no longer referenced and will not call any of the delegate methods.
Make requestManager an instance variable:
let requestManager = FacebookPostRouteRequest()
#IBAction func shareButtonPressed(_ sender: Any) {
requestManager.delegate = self
requestManager.showShareBadgeDialog(self.badge!, onViewController: self)
}
Your issues with the debugger are probably race conditions for stopping the main thread.