Delegate methods not being called in swift class - ios

I'm integrating an SDK that tracks location points.
The SDK demo app has the SDK framework loaded, and then a UIViewController that extends the SDK delegate. The SDK calls certain delegate functions as the user moves around - these all work fine in the demo app from the SDK provider. All the delegate callback functions, like processStart are inside the TripsViewController: UIViewController, TheSDKSwiftDelegate
Example:
import TheSDKSwift
final class TripsViewController: UIViewController, TheSDKSwiftDelegate {
...
TheSDKSwift.setup(with: configuration, delegate: self, completionHandler: {success, error in
if success {
successBlock()
} else {
failureBlock(error)
}
})
...
// TheSDK Delegate callbacks
func processStart(ofDrive startInfo: DriveStartInfo) {
self.driveStatusLabel.text = "Driving"
if self.isAccidentEnabled() {
self.mockAccidentButton.isEnabled = true
}
let dateString = DateFormatter.shortStyleFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(startInfo.startTimestamp/1000 )))
NotificationManager.displayNotification(message: "Trip Started: \(dateString)")
}
}
So during the normal running of the SDK, it then calls back to the processStart method to provide info on what's happening.
Now, in my own app, I can't have these functions/delegates in UIViewController. I need to have them in a separate Swift file and call the functions from another controller. When I do that, my SDK initializes, but the delegate methods don't get called by the SDK while it's running, whereas the delegate methods DO get called when everything is in on UIViewController extending the delegate. Just not when I put it in my own swift file.
Here's a short snippet of what I'm doing:
import Foundation
import Capacitor
#objc(TheSDKPlugin)
public class TheSDKPlugin: CAPPlugin {
#objc func SetupSDK(_ call: CAPPluginCall) {
TheSDKPluginWrapper().StartSDK()
}
So TheSDKPluginiWrapper().StartSDK() gets called.
import Foundation
import TheSDKSwift
import Capacitor
class TheSDKPluginWrapper: NSObject, TheSDKSwiftDelegate {
...
TheSDKSwift.setup(with: configuration, delegate: self, completionHandler: {success, error in
if success {
successBlock()
} else {
failureBlock(error)
}
})
...
// TheSDK Delegate callbacks
func processStart(ofDrive startInfo: DriveStartInfo) {
self.driveStatusLabel.text = "Driving"
if self.isAccidentEnabled() {
self.mockAccidentButton.isEnabled = true
}
let dateString = DateFormatter.shortStyleFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(startInfo.startTimestamp/1000 )))
NotificationManager.displayNotification(message: "Trip Started: \(dateString)")
}
}
Again, SDK initializes successfully. But now, SDK never calls the delegate method in TheSDKPluginiWrapper().
How do I retain the delegate throughout, so that SDK delegate methods get called in my swift file, same way it gets called when everything is in UIViewController?

If you want the delegate methods in a separate file, you can just define them as an extension to the class:
extension TripsViewController: TheSDKSwiftDelegate {
// TheSDK Delegate callbacks
func processStart(ofDrive startInfo: DriveStartInfo) {
}
...
}

Related

How can I return the result of a "GTLRSheetsQuery_SpreadsheetsValuesGet" query after it finishes execution (Swift using Sheets API)?

I'm trying to create an inventory management app for iOS using Swift and the Google Sheets API. Before starting that, I want to get acquainted with the API and make sure everything works properly. To do that, I want to create a function getCell that returns the contents of a cell at a hardcoded row and column (given in A1 notation as required). Since the Sheets API's implementation in Swift seems to require a callback model (I'm a beginner on callbacks and async programming in general), I haven't figured out how to return the cell's contents in my function after the query finishes.
I've followed the Sheets API iOS Quickstart provided by Google (https://developers.google.com/sheets/quickstart/ios?ver=swift&hl=zh-cn) to enable the API in the Developers Console and install the necessary Cocoapods (my Podfile installs 'GoogleAPIClientForREST/Sheets' and 'GoogleSignIn'). I got the included code to run and work properly after converting some parts to newer Swift syntax.
The issue arose when I wrote my getCell function in the ViewController.swift file. As can be seen in the code below, I assumed that the query would finish by the time getCellQuery finishes, so that getCell would be able to access the global variable updated by the callback function (displayResultWithTicket). However, the debug print statements I put in show that the callback function only executes at the end of the program, after getCell checks the global variable and finds nothing new. Note that the spreadsheet ID needs to be replaced with a real one to run this code.
import GoogleAPIClientForREST
import GoogleSignIn
import UIKit
struct MyVariables {
static var currentCell: String? = nil
}
class ViewController: UIViewController, GIDSignInDelegate, GIDSignInUIDelegate {
private let scopes = [kGTLRAuthScopeSheetsSpreadsheets]
private let service = GTLRSheetsService()
#IBOutlet weak var signInButton: GIDSignInButton!
override func viewDidLoad() {
super.viewDidLoad()
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().scopes = scopes
}
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
print("\(error.localizedDescription)")
self.service.authorizer = nil
} else {
self.signInButton.isHidden = true
self.service.authorizer = user.authentication.fetcherAuthorizer()
//main program starts here
print("Cell: " + getCell(a1: "Inventory!A1"))
}
}
func getCell(a1: String) -> String {
print("Started getCell")
getCellQuery(a1: a1)
if MyVariables.currentCell != nil {
print("Finished getCell")
return MyVariables.currentCell!
} else {
print("Error: the global variable is nil and probably hasn't been changed")
print("Finished getCell")
return ""
}
}
func getCellQuery(a1: String) {
print("Started getCellQuery")
let spreadsheetId = "INSERT ID HERE"
// arbitrary row and column in A1 notation
let getRange = a1
let getQuery = GTLRSheetsQuery_SpreadsheetsValuesGet.query(withSpreadsheetId: spreadsheetId, range:getRange)
service.executeQuery(getQuery, delegate: self, didFinish: #selector(displayResultWithTicket(ticket:finishedWithObject:error:)))
print("Finished getCellQuery")
}
#objc func displayResultWithTicket(ticket: GTLRServiceTicket,
finishedWithObject result : GTLRSheets_ValueRange,
error : NSError?) {
print("Started callback")
if let error = error {
print("\(error.localizedDescription)")
return
}
// turn 2d array into string
let rows = result.values!
for row in rows {
for item in row {
// update global variable
MyVariables.currentCell = item as? String
}
}
print("Finished callback")
}
}
After signing in, I would expect the following in the console:
Started getCell
Started getCellQuery
Started callback
Finished callback
Finished getCellQuery
Finished getCell
Cell: <Whatever the cell has>
Instead, I get this:
Started getCell
Started getCellQuery
Finished getCellQuery
Error: the cell value is nil and probably hasn't been changed
Finished getCell
Cell:
Started callback
Finished callback
Separately, I've also tried using a while loop to wait for the global variable to be updated, but it seems like Swift is consistently opting to call the callback function at the end of the entire program, which is annoying.
If there's a less clunky approach for this simple task, it would be great to know.
It looks like your issue is with async calling patterns. When you call service.executeQuery, it will begin an async query but since it is hitting a web service, that query could take a long time to complete. The executeQuery function returns immediately and the query itself is probably queued by their library. At the next opportunity, the request is sent and when the reply comes back, your displayResultWithTicket(ticket:finishedWithObject:error:) callback is called but in the meantime, the rest of your getCellQuery function completed, as did getCell.
A better pattern would be to fire some notification event in the callback now that you have a response (and pass the value you got back in the notification) and then have a listener for that notification get the response and do the appropriate thing with the result.
Alternatively, there may be a method in the sheets API to pass in a closure which is a lot like a callback but the code for it lives within the function that is passing it so state management is a little easier. I'm not familiar with that API so I'm not sure if that exists.
Here is an article on managing async code in Swift that covers some of the options. It's a little old, but it covers the basics.

Flutter: disable screenshot capture for app

I am making a Flutter app and I need to make sure the user is not able to capture screenshot of the app (any screen). Is there any way to achieve this in Flutter or do I need to write native code for both Android and IOS?
Android
Method 1 (all app screens):
Locate your MainActivity (.java or .kt) class inside the embedded android project dir in your Flutter Project,
Add the following import to your main activity class:
import android.view.WindowManager.LayoutParams;
Add the following line to your MainActivity's onCreate method:
getWindow().addFlags(LayoutParams.FLAG_SECURE);
Method 2 (for specific screens):
Use FlutterWindowManagerPlugin:
https://pub.dev/packages/flutter_windowmanager
Thanks, #Kamlesh!
It's only in iOS,just modify in AppDelegate.
And no more plugins
import UIKit
import Flutter
import Firebase
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
self.window.makeSecure() //Add this line
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
//And this extension
extension UIWindow {
func makeSecure() {
let field = UITextField()
field.isSecureTextEntry = true
self.addSubview(field)
field.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
field.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
self.layer.superlayer?.addSublayer(field.layer)
field.layer.sublayers?.first?.addSublayer(self.layer)
}
}
See image here
For Flutter2 project
Method 1 : using package flutter_windowmanager
Method 2 :
in Android with kotlin
Step 1 Open the file "mainActivity.kt" using the path
android\app\src\main\kotlin\com\example\auth_email\MainActivity.kt
Step 2 Import Library
import android.view.WindowManager.LayoutParams
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
Step 3 In main activity class
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
window.addFlags(LayoutParams.FLAG_SECURE)
super.configureFlutterEngine(flutterEngine)
}
}
In iOS Swift : AppDelegate.swift file
import UIKit
import Flutter
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// <Add>
override func applicationWillResignActive(
_ application: UIApplication
) {
self.window.isHidden = true;
}
override func applicationDidBecomeActive(
_ application: UIApplication
) {
self.window.isHidden = false;
}
}
The simplest way to do this is to use a flutter package called flutter_windowmanager
Works only for Android, not for IOS!
First import its latest version in pubspec.yaml file of your Flutter project and run pub get. Then add the below code inside the widget's initState() method for which you want to disable screenshot and screen recording.
Future<void> secureScreen() async {
await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
}
#override
void initState() {
secureScreen();
super.initState();
}
#override
void dispose(){
super.dispose();
await FlutterWindowManager.clearFlags(FlutterWindowManager.FLAG_SECURE);
}
If you want to make your whole app screenshot disable just call securescreen() method (defined above) inside your main() function in main.dart file.
What worked for me was writing the below code in MainActivity.java file.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
and importing these packages!
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.os.Bundle; // required for onCreate parameter
On iOS I have disabled taking of screenshots with the help of extension https://stackoverflow.com/a/67054892/4899849. Follow next steps:
Add property in AppDelegate:
var field = UITextField()
in didFinishLaunchingWithOptions call next method: addSecuredView()
private func addSecuredView() {
if (!window.subviews.contains(field)) {
window.addSubview(field)
field.centerYAnchor.constraint(equalTo: window.centerYAnchor).isActive = true
field.centerXAnchor.constraint(equalTo: window.centerXAnchor).isActive = true
window.layer.superlayer?.addSublayer(field.layer)
field.layer.sublayers?.first?.addSublayer(window.layer)
}
}
override delegate methods:
override func applicationWillResignActive(_ application: UIApplication) {
field.isSecureTextEntry = false
}
override func applicationDidBecomeActive(_ application: UIApplication) {
field.isSecureTextEntry = true
}
Now, when you make a screenshot in the app or record a screen video you will see a black image or video. Hope, it will help cuz I spent 2 days trying to make it work)
Flutter
Method 1: Using this package screen_protector
Method 2:
In Whole your Application
Open AppDelegate file and add UITextField variable.
private var textField = UITextField()
Create a function in AppDelegate file.
// Screenshot Prevent Functions
private func makeSecureYourScreen() {
if (!self.window.subviews.contains(textField)) {
self.window.addSubview(textField)
textField.centerYAnchor.constraint(equalTo: self.window.centerYAnchor).isActive = true
textField.centerXAnchor.constraint(equalTo: self.window.centerXAnchor).isActive = true
self.window.layer.superlayer?.addSublayer(textField.layer)
textField.layer.sublayers?.first?.addSublayer(self.window.layer)
}
}
Call this method into the didFinishLaunchingWithOptions function.
makeSecureYourScreen()
Code Screenshot
In Specific Screen - Using Method Channel
Open AppDelegate file and add UITextField variable.
private var textField = UITextField()
Create a function in AppDelegate file.
// Screenshot Prevent Functions
private func makeSecureYourScreen() {
if (!self.window.subviews.contains(textField)) {
self.window.addSubview(textField)
textField.centerYAnchor.constraint(equalTo: self.window.centerYAnchor).isActive = true
textField.centerXAnchor.constraint(equalTo: self.window.centerXAnchor).isActive = true
self.window.layer.superlayer?.addSublayer(textField.layer)
textField.layer.sublayers?.first?.addSublayer(self.window.layer)
}
}
Call this method into the didFinishLaunchingWithOptions function.
makeSecureYourScreen()
Also, add your method channel code in the didFinishLaunchingWithOptions function.
let controller : FlutterViewController = self.window?.rootViewController as! FlutterViewController
let securityChannel = FlutterMethodChannel(name: "secureScreenshotChannel", binaryMessenger: controller.binaryMessenger)
securityChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: #escaping FlutterResult) -> Void in
if call.method == "secureiOS" {
self.textField.isSecureTextEntry = true
} else if call.method == "unSecureiOS" {
self.textField.isSecureTextEntry = false
}
})
Add your code below code to your flutter files to disable the screenshot on a specific screen.
// Declare your method channel varibale here
var iosSecureScreenShotChannel = const MethodChannel('secureScreenshotChannel');
Now add code to initState to prevent screenshot
#override
void initState() {
// this method to user can't take screenshots of your application
iosSecureScreenShotChannel.invokeMethod("secureiOS");
// TODO: implement initState
super.initState();
}
For add code to dispose to allow screenshots on another screen.
#override
void dispose() {
// this method to the user can take screenshots of your application
iosSecureScreenShotChannel.invokeMethod("unSecureiOS");
// TODO: implement dispose
super.dispose();
}
Code Screenshot in Xcode
Code Screenshot in Flutter
You can disable screenshots and video captures like the Netflix app and Disney Hotstar app.
I have tried it in my application and it works fine. 😊
Screenshots can be prevented very easily by following below two steps.
I am using VS code.
Step 1 Open the file "mainActivity.kt" using the path android\app\src\main\kotlin\com\example\auth_email\MainActivity.kt
Step 2 Add the two lines
(a) import android.view.WindowManager.LayoutParams;
(b) getWindow().addFlags(LayoutParams.FLAG_SECURE); in MainActivity: FlutterActivity() section
Restart the app
enter image description here
This works for iOS. In your Runner > AppDelegate.m:
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)applicationWillResignActive:(UIApplication *)application{
self.window.hidden = YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application{
self.window.hidden = NO;
}
#end
If you are using kotlin
open MainActivity.kt
Add below code at end of imports
import android.view.WindowManager.LayoutParams
Add below code at end of super.onCreate(savedInstanceState)
window.addFlags(LayoutParams.FLAG_SECURE)
Its done.
try to use
for android edit MainActivity.kt
package com.package.app_name
import android.view.WindowManager.LayoutParams
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
window.addFlags(LayoutParams.FLAG_SECURE)
super.configureFlutterEngine(flutterEngine)
}
}
define this package inside pubspec.yaml file
flutter_windowmanager: ^0.0.1+1
get dependencies
flutter pub get
You need to call a method of FlutterWindowManager using await and async.
You have to add a few lines of code in your StatefulWidget.
Future<void> secureScreen() async {
await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
}
#override
void initState() {
secureScreen();
super.initState();
}
https://pub.dev/packages/screen_protector
use this one. works for Android, iOS both.
In iOS, screenshot will be captured but output will be black screen.
You can use the flutter_windowmanager: ^0.2.0 package to disable screenshot capture in a Flutter app. To do this, add the following code in your main.dart file:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
runApp(MyApp());
}
This will add the FLAG_SECURE flag to your app, which will prevent
screenshots from being captured. Note that this will only work on
Android devices.
You could maybe listen for the screenshot keys on iOS and when the combination is being pressed blacken the screen.

Built.io integration failed in iOS application due to ambiguous method call error

I am trying to integrate built.io in iOS application using Xcode 9.1 and swift 4 but it failed due to ambiguous save call error, although I have only called this method once still it says ambiguous call. I am unable to identify the problem.
I referred this link to integrate sdk in iOS :
Code used is
import UIKit
import Contentstack
import BuiltIO
class ViewController: UIViewController {
func built(){
var builtApplication : BuiltApplication = Built.application(withAPIKey: "")
var pc : BuiltClass = builtApplication.class(withUID: "test")
var projectObject:BuiltObject = pc.object()
projectObject.save { (responseType, err) in //ambiguous error here on save call
if err != nil {
print("Err")
} else {
print("Success")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
I have installed sdk in project using cocoapod. Below image contains application screenshot that shows error. I am using BuiltObject class object to call save method but when I jump into save method it takes me to method of BuiltKeyStore class, and I completely don't understand why? Please help, Thanks in advance 🙂
You need to pass completionBlock as a parameter instead of inline.
Try the below code which works fine for Swift 3.2 & 4,
let completionBlock:BuiltRequestCompletionHandler = { (responseType:BuiltResponseType, error:Error?) in
if error != nil {
print("Err")
} else {
print("Success")
}
}
projectObject.save(completionBlock)
Give it a try that should definitely work.

How to implement Firebase Google Analytics in custom keyboard?

I want to implement Firebase google analytics in my custom keyboard for button click event.
Anyone please tell me how I can do this in swift.
Add firebase sdk and GoogleService-Info.plist file to keyboard extension target. Import firebase and configure it. FIRApp.configure() must be called only once per session:
import Firebase
class FirebaseConfigurator: NSObject {
static var onceToken: dispatch_once_t = 0
static func configure() {
dispatch_once(&onceToken) {
FIRApp.configure()
}
}
and
FirebaseConfigurator.configure()
FIRAnalytics.logEventWithName("button click event", parameters: nil).

Why is session.paired unavailable?

In an iOS app, I've created a swift file named DataCoordinator with the following code:
import UIKit
import WatchConnectivity
class DataCoordinator: NSObject {
func test(session:WCSession!) {
if session.paired == true {
//...
}
}
}
When I try to build, I get this error:
'paired' is unavailable
'paired' has been explicitly marked unavailable here
This is in the iOS app and not the in the WatchOS app. It works fine if I use the above code in a ViewController class.

Resources