I create a flutter app and need to add Siri button in my flutter app now, so I have to use FlutterPlatformView to host native iOS view.
Unfortunately, I am not familiar with native iOS code....so I still can't figure out what is problem right now.
I have already successfully run the FlutterPlatformView without "INUIAddVoiceShortcutButtonDelegate", but when I add the extension of INUIAddVoiceShortcutButtonDelegate, I got error in "present function"(like the image below).
Thanks a lot if anyone can give me a hint!
import UIKit
import IntentsUI
import Flutter
class AddSiriView:NSObject,FlutterPlatformView{
let frame: CGRect
let viewId:Int64
private var _view: UIView
init(_ frame:CGRect,viewId:Int64,args:Any?){
self.frame = frame
self.viewId = viewId
_view = UIView()
super.init()
addSiriButton(to: _view)
// iOS views can be added here
}
public func view() -> UIView {
return _view
}
func addSiriButton(to view: UIView) {
let button = INUIAddVoiceShortcutButton(style: .whiteOutline)
button.shortcut = INShortcut(intent: intent )
button.delegate = self
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
view.centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true
view.centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true
}
}
extension AddSiriView: INUIAddVoiceShortcutButtonDelegate {
func present(_ addVoiceShortcutViewController: INUIAddVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {
addVoiceShortcutViewController.delegate = self
addVoiceShortcutViewController.modalPresentationStyle = .formSheet
==============I always get error here===============================
present(addVoiceShortcutViewController, animated: true, completion: nil)
}
func present(_ editVoiceShortcutViewController: INUIEditVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {
editVoiceShortcutViewController.delegate = self
editVoiceShortcutViewController.modalPresentationStyle = .formSheet
==============I always get error here===============================
present(editVoiceShortcutViewController, animated: true, completion: nil)
}
}
extension AddSiriView: INUIAddVoiceShortcutViewControllerDelegate {
func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) {
controller.dismiss(animated: true, completion: nil)
}
}
extension AddSiriView: INUIEditVoiceShortcutViewControllerDelegate {
func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didUpdate voiceShortcut: INVoiceShortcut?, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didDeleteVoiceShortcutWithIdentifier deletedVoiceShortcutIdentifier: UUID) {
controller.dismiss(animated: true, completion: nil)
}
func editVoiceShortcutViewControllerDidCancel(_ controller: INUIEditVoiceShortcutViewController) {
controller.dismiss(animated: true, completion: nil)
}
}
extension AddSiriView {
public var intent: ConductIntent {
let testIntent = ConductIntent()
testIntent.action = "my test intent"
return testIntent
}
}
public class AddSiriViewFactory : NSObject,FlutterPlatformViewFactory{
public func create(
withFrame frame:CGRect,
viewIdentifier viewId:Int64,
arguments args: Any?
) -> FlutterPlatformView{
return AddSiriView(frame,viewId: viewId,args: args)
}
}
Related
I was using the code from the following site
https://www.hackingwithswift.com/example-code/media/how-to-scan-a-qr-code
The code works perfectly, the code can be viewed by accessing the link above.
It was a code that capture a QRCode/BarCode from camera and convert it to string.
The part of the the code that shows the string is:
func found(code: String) {
print(code)
}
After that the code string is "printed", the code calls "dismiss" and return to the previous UIViewController.
I want to get the "code" string and get the data to the previous UIViewController.
The only way that I am able to do that now is using the following code:
func found(code: String) {
print("code: \(code)")
ResenhaEquideoIdentificaAnimal1Controller.shared.microchipAnimalTextField.text = code
}
But this code only works if the code is called by the "ResenhaEquideoIdentificaAnimal1Controller" class.
I use the following code to call the new UIViewController inside the "ResenhaEquideoIdentificaAnimal1Controller" class using a UIButton.
let myScannerViewController = MyScannerViewController()
present(myScannerViewController, animated: true, completion: nil)
How can I made this class reusable to be able to call the "MyScannerViewController" class
and send data back to the view that calls it?
You want to use a "delegate patten", that is, when the code is found or something went wrong, you delegate the functionality to some other party to deal with it.
For example, you could modify the existing example to add support for a simple delegate...
import AVFoundation
import UIKit
protocol ScannerDelegate: AnyObject {
func scanner(_ controller: ScannerViewController, didDiscoverCode code: String)
func failedToScanner(_ controller: ScannerViewController)
}
class ScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
var captureSession: AVCaptureSession!
var previewLayer: AVCaptureVideoPreviewLayer!
weak var scannerDelegate: ScannerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.black
captureSession = AVCaptureSession()
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
let videoInput: AVCaptureDeviceInput
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
return
}
if (captureSession.canAddInput(videoInput)) {
captureSession.addInput(videoInput)
} else {
failed()
return
}
let metadataOutput = AVCaptureMetadataOutput()
if (captureSession.canAddOutput(metadataOutput)) {
captureSession.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
metadataOutput.metadataObjectTypes = [.qr]
} else {
failed()
return
}
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
captureSession.startRunning()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if (captureSession?.isRunning == false) {
captureSession.startRunning()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if (captureSession?.isRunning == true) {
captureSession.stopRunning()
}
}
private func failed() {
captureSession = nil
scannerDelegate?.failedToScanner(self)
}
private func didFind(code: String) {
scannerDelegate?.scanner(self, didDiscoverCode: code)
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
// MARK: AVCaptureMetadataOutputObjectsDelegate
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
captureSession.stopRunning()
if let metadataObject = metadataObjects.first {
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
guard let stringValue = readableObject.stringValue else { return }
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
didFind(code: stringValue)
}
}
}
When you want to scan something, your calling view controller could adopt the protocol...
extension ViewController: ScannerDelegate {
func failedToScanner(_ controller: ScannerViewController) {
controller.dismiss(animated: true) {
let ac = UIAlertController(title: "Scanning not supported", message: "Your device does not support scanning a code from an item. Please use a device with a camera.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)
}
}
func scanner(_ controller: ScannerViewController, didDiscoverCode code: String) {
codeLabel.text = code
controller.dismiss(animated: true)
}
}
and when you wanted to present the scanner view controller, you would simply set the view controller as the delegate...
let controller = ScannerViewController()
controller.scannerDelegate = self
present(controller, animated: true)
The great thing about this is, you could easily reject the code if you weren't interested in simply by modifying the delegate workflow
I'm trying to make Google social helper is NSObject outside of ViewController. I'm present SignIn using UIApplication extension in root ViewController, but I still have an error.
'uiDelegate must either be a |UIViewController| or implement the |signIn:presentViewController:| and |signIn:dismissViewController:| methods from |GIDSignInUIDelegate|.'
This my social helper object
import GoogleSignIn
class GidHelper: NSObject, GIDSignInUIDelegate, GIDSignInDelegate {
private let succesAuth: (String, String, String, String) -> ()
private let failedAuth: (Error) -> ()
init(succesAuth: #escaping (String, String, String, String) -> (), failedAuth: #escaping (Error) -> ()) {
self.succesAuth = succesAuth
self.failedAuth = failedAuth
super.init()
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().delegate = self
}
func openGidAuthorization() {
GIDSignIn.sharedInstance().signIn()
}
func gidLogout() {
GIDSignIn.sharedInstance().signOut()
}
// Present a view that prompts the user to sign in with Google
private func signIn(signIn: GIDSignIn!,
presentViewController viewController: UIViewController!) {
UIApplication.topViewController()?.present(viewController, animated: true, completion: nil)
}
private func signIn(signIn: GIDSignIn!,
dismissViewController viewController: UIViewController!) {
UIApplication.topViewController()?.dismiss(animated: true, completion: nil)
}
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
print("\(error.localizedDescription)")
self.failedAuth(error)
} else {
let userId = user.userID
let accessToken = user.authentication.accessToken
let userAvatarUrl = user.profile.imageURL(withDimension: 100)?.absoluteString
let email = user.profile.email
self.succesAuth(accessToken!, userId!, email!, userAvatarUrl!)
}
}
}
My UIApplication extension:
import Foundation
import UIKit
extension UIApplication {
class func topViewController(controller: UIViewController? =
UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
}
Your implementation of GidHelper class look very weird starting from the init and finishing with this extension:(. I recommend you to create a service (let it be your GoogleLoginService), make it a singleton and create also a NavigationService instead of this mess with extension. Here is some ideas how to implement this:
import GoogleSignIn
final class GoogleLoginService: NSObject {
typealias SignInResponse = (_ user: User?, _ error: Error?) -> ()
static let sharedInstance = GoogleLoginService()
private var presenter: UIViewController?
private var singInCompletion: SignInResponse?
//Call next function in appDelegate: didFinishLaunchingWithOptions
#discardableResult func registerInApplication(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let url = Bundle.main.url(forResource: "GoogleService-Info", withExtension: "plist"),
let data = try? Data(contentsOf: url) {
let dictionary = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String : AnyObject]
if let clientID = dictionary??["CLIENT_ID"] {
GIDSignIn.sharedInstance().clientID = clientID as? String
}
}
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
return true
}
// Call this function in AppDelegate: open url
#discardableResult func handleURLIn(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
return GIDSignIn.sharedInstance().handle(url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String, annotation: options[UIApplication.OpenURLOptionsKey.annotation])
}
// MARK: - UserManagement
func signIn(_ controller: UIViewController, completion: SignInResponse?) {
singInCompletion = completion
presenter = controller
GIDSignIn.sharedInstance().signIn()
}
func signOut() {
GIDSignIn.sharedInstance().signOut()
}
func isLoggedIn() -> Bool {
return GIDSignIn.sharedInstance().hasAuthInKeychain()
}
}
extension GoogleLoginService: GIDSignInDelegate {
// MARK: - GIDSignInDelegate
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
self.singInCompletion?(nil, error)
return
}
guard let authentication = user.authentication else {
self.singInCompletion?(nil, error)
return
}
let googleUserObj = User(name: user.profile.name) // <-- You can get your user data
}
}
extension GoogleLoginService: GIDSignInUIDelegate {
// MARK: - GIDSignInUIDelegate
func sign(_ signIn: GIDSignIn!, present viewController: UIViewController!) {
presenter?.present(viewController, animated: true, completion: nil)
}
func sign(_ signIn: GIDSignIn!, dismiss viewController: UIViewController!) {
presenter = nil
viewController.dismiss(animated: true, completion: nil)
}
}
Now using isLoggedIn method you can save a result in lets say UserDefaults and using NavigationService check if user is logged in or not and go to a proper view controller (as example have a look on the following method of NavigavionService:
func presentCurrentUserUI() {
// next line is extension on UserDefaults which keep Bool value - result of logging procedure
if UserDefaults().isLoggedIn {
let homeViewController = UIStoryboard(name: StoryboardName.main, bundle: nil).instantiateInitialViewController()
self.window?.rootViewController = homeViewController
} else {
let loginViewController = UIStoryboard(name: StoryboardName.login, bundle: nil).instantiateInitialViewController()
self.window?.rootViewController = loginViewController
}
self.window?.makeKeyAndVisible()
}
Hope it will help you to refactor your code and avoid this error:) Good luck!
I created a class « Chargement »which allows to display an alert with a loading and to close it.
import Foundation
class Chargement {
var alert : UIAlertController;
var message : String = NSLocalizedString("PleaseWait", comment:"") ;
let loadingIndicator : UIActivityIndicatorView;
init(message : String?) {
if(message != nil){
self.message = message!;
}
self.alert = UIAlertController(title: nil, message: self.message, preferredStyle: UIAlertControllerStyle.alert)
self.loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 4, width: 40, height: 60))
}
func showLoading(view : UIViewController){
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
view.present(alert, animated: true, completion: nil)
}
func closeLoading(){
alert.dismiss(animated: true, completion: nil)
}
}
When I use my class "Chargement" in a controllerView without UICollectionView, it works for example :
class ViewController : UIViewController{
let loadingView : Chargement = Chargement(message: nil);
override func viewDidLoad() {
super.viewDidLoad();
loadingView.showLoading(view: self);
}
#IBAction func btn_Action(_ sender: UIButton) {
self.loadingView.closeLoading();//it works
}
}
But when I use it in a controllerView with UICollectionView the alert display works but it doesn’t close.
class TTwoViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
let loadingView : Chargement = Chargement(message: nil);
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad();
loadingView.showLoading(view: self);
loadingData();
}
func loadingData() {
dataManager().getData(num: 1, completion: {
(data, messages) in
self.loadingView.closeLoading(); //it don't work
DispatchQueue.main.async(execute: {
self.collectionView.reloadData();
//self.loadingView.closeLoading(); //it don't work
})
})
}
}
If you just want to show an activity indicator, you can simplify the whole process by using NVActivityIndicatorView:
override func viewDidLoad() {
super.viewDidLoad();
let activityData = ActivityData()
NVActivityIndicatorPresenter.sharedInstance.startAnimating(activityData)
NVActivityIndicatorPresenter.sharedInstance.setMessage("Please Wait")
loadingData();
}
func loadingData() {
dataManager().getData(num: 1, completion: { (data, messages) in
DispatchQueue.main.async {
self.collectionView.reloadData()
NVActivityIndicatorPresenter.sharedInstance.stopAnimating()
}
})
}
I currently have Game Center working in my iOS target, but not working in the tvOS target.
I've already added the Leaderboard images and placed the identifier in Xcode here:
This is the class that I'm using to bring up the leaderboard and initiate the player. It's called GameKitHelper.swift:
import UIKit
import Foundation
import GameKit
let PresentAuthenticationViewController = "PresentAuthenticationViewController"
class GameKitHelper: NSObject {
static let sharedInstance = GameKitHelper()
var authenticationViewController: UIViewController?
var gameCenterEnabled = false
func authenticateLocalPlayer() {
//1
let localPlayer = GKLocalPlayer()
localPlayer.authenticateHandler = {(viewController, error) in
if viewController != nil {
//2
self.authenticationViewController = viewController
NSNotificationCenter.defaultCenter().postNotificationName(PresentAuthenticationViewController, object: self)
}
else if error == nil {
//3
self.gameCenterEnabled = true
}
}
}
func reportAchievements(achievements: [GKAchievement], errorHandler: ((NSError?)->Void)? = nil) {
guard gameCenterEnabled else {
return
}
GKAchievement.reportAchievements(achievements, withCompletionHandler: errorHandler)
}
func showGKGameCenterViewController(viewController: UIViewController) {
guard gameCenterEnabled else {
return
}
//1
let gameCenterViewController = GKGameCenterViewController()
//2
gameCenterViewController.gameCenterDelegate = self
//3
viewController.presentViewController(gameCenterViewController, animated: true, completion: nil)
}
func saveHighScore(identifier: String, score: Int) {
if (GKLocalPlayer.localPlayer().authenticated) {
let scoreReporter = GKScore(leaderboardIdentifier: identifier)
scoreReporter.value = Int64(score)
let scoreArray:[GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: {
error -> Void in
if (error != nil) {
print("error")
}
else {
print("Posted score of \(score)")
}
})
}
}
}
extension GameKitHelper: GKGameCenterControllerDelegate {
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
}
NavigationController class:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self,
selector: Selector("showAuthenticationViewController"),
name: PresentAuthenticationViewController,
object: nil)
GameKitHelper.sharedInstance.authenticateLocalPlayer()
}
func showAuthenticationViewController() {
let gameKitHelper = GameKitHelper.sharedInstance
if let authenticationViewController = gameKitHelper.authenticationViewController {
topViewController?.presentViewController(authenticationViewController,
animated: true,
completion: nil)
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Make sure you have an "Apple TV Dashboard Image" in your app's asset catalog. The dashboard artwork should be 923x150 with transparency, and it doesn't need to be layered since the user can't select it.
The tvOS Human Interface Guidelines claim that the dashboard artwork is optional, but in my experience GKGameCenterViewController will refuse to appear if you don't have one.
I am making SignUp/SignIn pages with watching youtube video.
He makes these pages with parseUI,
In his video, there is no error, but I get an error which is
"binary operator "|" cannot be applied to two 'PFLoginFields' operands" (It is checked where in my code down there. )
I checked in parse.com, parse example code is exactly same code with his.
So my Question is
How can I fix this code to work properly?
or Is there any other Binary operator I can use instead of ||?
import UIKit
import Parse
import ParseUI
class NewResisterVC: UIViewController, PFLogInViewControllerDelegate, PFSignUpViewControllerDelegate {
var logInViewController : PFLogInViewController = PFLogInViewController()
var signUpViewController : PFSignUpViewController = PFSignUpViewController()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if (PFUser.currentUser() == nil) {
/////////// HERE IS A PROBELM /////////////////
self.logInViewController.fields = (PFLogInFields.UsernameAndPassword | PFLogInFields.LogInButton | PFLogInFields.SignUpButton | PFLogInFields.PasswordForgotten | PFLogInFields.DismissButton)
//////////////////////////////////////////
var loginLogoTitle = UILabel()
loginLogoTitle.text = "bany"
self.logInViewController.logInView!.logo = loginLogoTitle
self.logInViewController.delegate = self
var signUpLogoTitle = UILabel()
signUpLogoTitle.text = "bany"
self.signUpViewController.signUpView!.logo = signUpLogoTitle
self.signUpViewController.delegate = self
self.logInViewController.signUpController = self.signUpViewController
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: Parse Login
func logInViewController(logInController: PFLogInViewController, shouldBeginLogInWithUsername username: String, password: String) -> Bool {
if(!username.isEmpty || !password.isEmpty) {
return true
}else{
return false
}
}
func logInViewController(logInController: PFLogInViewController, didLogInUser user: PFUser) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?) {
print("Fail to login")
}
//MARK: Parse Sign Up
func signUpViewController(signUpController: PFSignUpViewController, didSignUpUser user: PFUser) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func signUpViewController(signUpController: PFSignUpViewController, didFailToSignUpWithError error: NSError?) {
print("fail to sign up...")
}
func signUpViewControllerDidCancelSignUp(signUpController: PFSignUpViewController) {
print("User dismissed sign up")
}
// mark: Actions
#IBAction func simpleAction(send: AnyObject) {
self.presentViewController(self.logInViewController, animated: true, completion: nil)
}
}
The guy in video is using xcode 6 with swift 1.2, you are using xcode 7 with swift 2.0. Syntax has changed, you have to rewrite this line like this:
self.logInViewController.fields = [PFLogInFields.UsernameAndPassword,
PFLogInFields.LogInButton, PFLogInFields.SignUpButton,
PFLogInFields.PasswordForgotten, PFLogInFields.DismissButton]
Also note that you don't need to write typename prefix when assigning to variable:
self.logInViewController.fields = [.UsernameAndPassword, .LogInButton,
.SignUpButton, .PasswordForgotten, .DismissButton]