Callback #escaping function objective-c from swift [closed] - objective-c

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 3 years ago.
Improve this question
I am trying to implement the callback of a Swift function in Objective-C, but it does not show me the following error, "No visible #interface for 'AccessControl' declares the selector 'evaluateWithCompletion:'" which can be seen in the image in more detail.
I'm using Swift 5 and Xcode 11.3
The AccessControl code is:
import Foundation
import LocalAuthentication
#objc open class AccessControl: NSObject {
// Singleton
#objc public static let shared = AccessControl()
// Private
private override init() {}
// Policy
private var policy: LAPolicy = .deviceOwnerAuthentication
// Reason
private var reason: String = "reason"
#objc static func isAuthenticationSupported() -> Bool {
if #available(iOS 11.0, *) {
let localAuthenticationContext = LAContext()
if localAuthenticationContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) {
if #available(iOS 11.2, *) {
return localAuthenticationContext.biometryType != .none
} else {
return localAuthenticationContext.biometryType != .LABiometryNone
}
}
}
return false
}
#objc func evalute(completion: #escaping ((Bool) -> Void)) {
var authError: NSError?
guard context.canEvaluatePolicy(policy, error: &authError) else {
completion(false)
return
}
context.evaluatePolicy(policy, localizedReason: reason) { success, evaluateError in
if success {
completion(true)
} else {
completion(false)
}
}
}
}
What am I doing wrong?

#objc func evalute(completion: #escaping ((Bool) -> Void))
I assume the declaration should be
#objc func evaluate(completion: #escaping (Bool) -> Void)

Related

How i can use Flow on swift Kotlin multiplatform?

I am creating my first kotlin multiplataform project, and i'm having some difficulty to use the kotlin flow on swift. I created the models, server data requests as common file using kotlin flow and ktor, the the view model and ui layers i am creating as native. So, i have no experience with swift development, and beyond that i' having a lot of trouble to use flow on swift view model. Searching for an answer to my problem i found a class described as CommonFlow, which aims to be used as a common code for both languages(kotlin, swift, but i'm having an error that gives me little or no clue as to why it happens or, probably, it's just my lack of dominion with xcode and swift programming:
So this is the code part that xcode points the error:
Obs: sorry about the image i thought that maybe this time it would be more descriptive
And this its all i got from the error
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee5928ff8)
MY Ios ViewModel:
class ProfileViewModel: ObservableObject {
private let repository: ProfileRepository
init(repository: ProfileRepository) {
self.repository = repository
}
#Published var authentication: Authetication = Authetication.unauthenticated(false)
#Published var TokenResponse: ResponseDTO<Token>? = nil
#Published var loading: Bool = false
func authenticate(email: String, password: String) {
DispatchQueue.main.async {
if(self.isValidForm(email: email, password: password)){
self.repository.getTokenCFlow(email: email, password: password).watch{ response in
switch response?.status {
case StatusDTO.success:
self.loading = false
let token: String = response!.data!.accessToken!
SecretStorage().saveToken(token: token)
self.authentication = Authetication.authenticated
break;
case StatusDTO.loading:
self.loading = true
break;
case StatusDTO.error:
print("Ninja request error \(String(describing: response!.error!))}")
break;
default:
break
}
}
}
}
}
private func isValidForm(email: String, password: String) -> Bool {
var invalidFields = [Pair]()
if(!isValidEmail(email)){
invalidFields.append(Pair(first:"email invalido",second: "email invalido"))
}
if(password.isEmpty) {
invalidFields.append(Pair(first:"senha invalida",second: "senha invalida"))
}
if(!invalidFields.isEmpty){
self.authentication = Authetication.invalidAuthentication(invalidFields)
return false
}
return true
}
private func isValidEmail(_ email: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailPred.evaluate(with: email)
}
}
class Pair {
let first: String
let second: String
init(first:String, second: String) {
self.first = first
self.second = second
}
}
enum Authetication {
case invalidAuthentication([Pair])
case authenticated
case persistentAuthentication
case unauthenticated(Bool)
case authenticationFailed(String)
}
The repository methods:
override fun getToken(email: String, password: String): Flow<ResponseDTO<Token>> = flow {
emit(ResponseDTO.loading<Token>())
try {
val result = api.getToken(GetTokenBody(email, password))
emit(ResponseDTO.success(result))
} catch (e: Exception) {
emit(ResponseDTO.error<Token>(e))
}
}
#InternalCoroutinesApi
override fun getTokenCFlow(email: String, password: String): CFlow<ResponseDTO<Token>> {
return wrapSwift(getToken(email, password))
}
The Class CFLOW:
#InternalCoroutinesApi
class CFlow<T>(private val origin: Flow<T>): Flow<T> by origin {
fun watch(block: (T) -> Unit): Closeable {
val job = Job()
onEach {
block(it)
}.launchIn(CoroutineScope(Dispatchers.Main + job))
return object: Closeable {
override fun close() {
job.cancel()
}
}
}
}
#FlowPreview
#ExperimentalCoroutinesApi
#InternalCoroutinesApi
fun <T> ConflatedBroadcastChannel<T>.wrap(): CFlow<T> = CFlow(asFlow())
#InternalCoroutinesApi
fun <T> Flow<T>.wrap(): CFlow<T> = CFlow(this)
#InternalCoroutinesApi
fun <T> wrapSwift(flow: Flow<T>): CFlow<T> = CFlow(flow)
There is an example of using flows in KampKit
https://github.com/touchlab/KaMPKit
I will paste an excerpt from NativeViewModel (iOS)
class NativeViewModel(
private val onLoading: () -> Unit,
private val onSuccess: (ItemDataSummary) -> Unit,
private val onError: (String) -> Unit,
private val onEmpty: () -> Unit
) : KoinComponent {
private val log: Kermit by inject { parametersOf("BreedModel") }
private val scope = MainScope(Dispatchers.Main, log)
private val breedModel: BreedModel = BreedModel()
private val _breedStateFlow: MutableStateFlow<DataState<ItemDataSummary>> = MutableStateFlow(
DataState.Loading
)
init {
ensureNeverFrozen()
observeBreeds()
}
#OptIn(FlowPreview::class)
fun observeBreeds() {
scope.launch {
log.v { "getBreeds: Collecting Things" }
flowOf(
breedModel.refreshBreedsIfStale(true),
breedModel.getBreedsFromCache()
).flattenMerge().collect { dataState ->
_breedStateFlow.value = dataState
}
}
This ViewModel is consumed in swift like this:
lazy var adapter: NativeViewModel = NativeViewModel(
onLoading: { /* Loading spinner is shown automatically on iOS */
[weak self] in
guard let self = self else { return }
if (!(self.refreshControl.isRefreshing)) {
self.refreshControl.beginRefreshing()
}
},
onSuccess: {
[weak self] summary in self?.viewUpdateSuccess(for: summary)
self?.refreshControl.endRefreshing()
},
onError: { [weak self] error in self?.errorUpdate(for: error)
self?.refreshControl.endRefreshing()
},
onEmpty: { /* Show "No doggos found!" message */
[weak self] in self?.refreshControl.endRefreshing()
}
)
In short, the flow is kept wrapped in kotlin mp land and leveraged in iOS by using traditional callback interfaces.

SwiftUI determine initial view

I'm trying to figure out the navigation in the beginning of an app I'm making in the SwiftUI lifecycle. Depending on settings determined at launch, the user may start in one of three views. If the user opens the app from a push notification, they should see the video view (WrappedVideoViewController). Otherwise, if they are logged in already, they should see the Home view, or if not, the Login view. I'm using ContentView as the parent:
import SwiftUI
struct ContentView: View {
#EnvironmentObject var notifService: NotificationService
#EnvironmentObject var authState: AuthenticationState
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#ObservedObject var viewRouter: ViewRouter
init() {
self.viewRouter = ViewRouter.shared
}
var body: some View {
print("in ContentView body")
switch viewRouter.currentScreen {
case .home:
print("home")
return AnyView(Home(innerGradientColor: Color.homeActiveInner, outerGradientColor: Color.homeActiveOuter))
case .login:
print("login")
return
AnyView(Login()
.environmentObject(AuthenticationState.shared))
case .video:
print("video")
return AnyView(WrappedVideoViewController())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(AuthenticationState.shared)
.environmentObject(NotificationService())
}
}
My ViewRouter class is an ObservableObject meant to control which screen is shown. Currently, I have this:
//
// NavigationState.swift
// BumpCall-SwiftUI
//
// Created by Ben on 1/22/21.
//
import Foundation
import SwiftUI
enum Screen {
case home
case video
case login
}
class ViewRouter: ObservableObject {
var authState: AuthenticationState
var notifService: NotificationService
static var shared = ViewRouter(authState: AuthenticationState.shared, notifService: NotificationService.shared)
init(authState: AuthenticationState, notifService: NotificationService) {
self.authState = authState
self.notifService = notifService
if notifService.dumbData != nil {
currentScreen = .video
} else if (notifService.dumbData == nil && authState.isLoggedIn()) {
currentScreen = .home
} else {
currentScreen = .login
}
}
#Published var currentScreen: Screen
}
It's tricky because the ViewRouter is dependent on two other ObservableObjects, which I'm trying to handle here with the init(). It appears that the AuthenticationState class is working fine because upon opening the app, it does send you to the home screen if you are logged in. However, opening from a notification also sends me to the home screen. NotificationService changes the ViewRouter singleton in the didReceive response: method:
class NotificationService: NSObject, ObservableObject {
#Published var dumbData: UNNotificationResponse?
static var shared = NotificationService()
override init() {
super.init()
UNUserNotificationCenter.current().delegate = self
}
}
extension NotificationService: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("presenting notification")
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
ViewRouter.shared.notifService.dumbData = response
print("Notification service didReceive response")
completionHandler()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) { }
}
But the singleton must not update for ContentView to notice. This is super convoluted and it's been confusing me for days, any help would be much appreciated.
The problem is that ViewRouter init() will be called once. As you mentioned, when the user is logged in, currentScreen will be initialized to .home. However this variable will never be changed afterwards.
What you basically want, is to set currentScreen to .video inside the Notification delegate.
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
ViewRouter.shared.currentScreen = .video //<< here set video
print("Notification service didReceive response")
completionHandler()
}

Is possibile to open a PDF in SwiftUI using Quicklook?

I am developing an application that integrates a button that must refer to display a pdf file. Is it possible to do this using QuickLook?
Best Regards,
Stefano
here's some sample code.
import SwiftUI
import QuickLook
struct PreviewController: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context: Context) -> UINavigationController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
let navigationController = UINavigationController(rootViewController: controller)
return navigationController
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, QLPreviewControllerDelegate, QLPreviewControllerDataSource {
let parent: PreviewController
init(parent: PreviewController) {
self.parent = parent
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return parent.url as NSURL
}
}
}

UIMenuItem #selector method crash in wkwebview

UIMenuItem selector method crashes in iOS 11 beta SDK.
-[WKContentView highlightText]: unrecognized selector sent to instance 0x7f85df8f3200
Method Definition:
func highlightText()
{
//
}
I try to add UIMenuItem in WKWebView,
let menuItemHighlight = UIMenuItem.init(title: "Highlight", action: #selector(ContentWebkitView.highlightText))
UIMenuController.shared.menuItems = [menuItemHighlight]
I was also getting this error when I was overriding canPerformAction and checking for my custom selector. In my case I wanted to remove all menu items except for my custom one and the following made this work for me.
class ViewController: UIViewController {
#IBOutlet weak var webView: MyWebView!
override func viewDidLoad() {
super.viewDidLoad()
loadWebView()
setupCustomMenu()
}
func loadWebView() {
let url = URL(string: "http://www.google.com")
let request = URLRequest(url: url!)
webView.load(request)
}
func setupCustomMenu() {
let customMenuItem = UIMenuItem(title: "Foo", action:
#selector(ViewController.customMenuTapped))
UIMenuController.shared.menuItems = [customMenuItem]
UIMenuController.shared.update()
}
#objc func customMenuTapped() {
let yay = "🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪🤪"
let alertView = UIAlertController(title: "Yay!!", message: yay, preferredStyle: .alert)
alertView.addAction(UIAlertAction(title: "cool", style: .default, handler: nil))
present(alertView, animated: true, completion: nil)
}
}
class MyWebView: WKWebView {
// turn off all other menu items
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
OK, we finally made it work for Swift 4:
In your WKWebView subclass, add the following property and method:
// MARK: - Swizzling to avoid responder chain crash
var wkContentView: UIView? {
return self.subviewWithClassName("WKContentView")
}
private func swizzleResponderChainAction() {
wkContentView?.swizzlePerformAction()
}
Then, add an extension to UIView (I put it in the same file as my WKWebView subclass, you can make it fileprivate if you'd like)
// MARK: - Extension used for the swizzling part linked to wkContentView
extension UIView {
/// Find a subview corresponding to the className parameter, recursively.
func subviewWithClassName(_ className: String) -> UIView? {
if NSStringFromClass(type(of: self)) == className {
return self
} else {
for subview in subviews {
return subview.subviewWithClassName(className)
}
}
return nil
}
func swizzlePerformAction() {
swizzleMethod(#selector(canPerformAction), withSelector: #selector(swizzledCanPerformAction))
}
private func swizzleMethod(_ currentSelector: Selector, withSelector newSelector: Selector) {
if let currentMethod = self.instanceMethod(for: currentSelector),
let newMethod = self.instanceMethod(for:newSelector) {
let newImplementation = method_getImplementation(newMethod)
method_setImplementation(currentMethod, newImplementation)
} else {
print("Could not find originalSelector")
}
}
private func instanceMethod(for selector: Selector) -> Method? {
let classType = type(of: self)
return class_getInstanceMethod(classType, selector)
}
#objc private func swizzledCanPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
Now the UIMenuItem works as expected:
But honestly, this really feels like a hack, and I would love Apple to fix this issue :-/
Thanks for Stephan Heilner for his answer: https://stackoverflow.com/a/42985441/4670400

How to pass variables from one View Controller to another in WatchOS 2 & Swift

I am having a lot of problems trying to get a couple of variables from one View Controller to the next. How can I do it properly?
Here's my code below. This is the view controller where I want to be able to send the variables RedScoreW and BlueScoreW to the next window. I am asking on HOW TO DO THIS using SWIFT language and specially for WATCHOS apps.
class InterfaceController2: WKInterfaceController {
var RedScoreW = 0
var BlueScoreW = 0
#IBOutlet var WatchRedScoreLabel: WKInterfaceLabel!
#IBOutlet var WatchBlueScoreLabel: WKInterfaceLabel!
#IBAction func RedScorePlus() {
if RedScoreW == 999 {
RedScoreW = 0
WatchRedScoreLabel.setText("0")
}else {
RedScoreW += 1
WatchRedScoreLabel.setText(String(RedScoreW))
}
}
#IBAction func RedScoreMinus() {
if RedScoreW == 0 {
RedScoreW = 999
WatchRedScoreLabel.setText("999")
}
else {
RedScoreW -= 1
WatchRedScoreLabel.setText(String(RedScoreW))
}
}
#IBAction func BlueScorePlus() {
if BlueScoreW == 999 {
BlueScoreW = 0
WatchBlueScoreLabel.setText("0")
} else{
BlueScoreW += 1
WatchBlueScoreLabel.setText(String(BlueScoreW))
}
}
#IBAction func BlueScoreMinus() {
if BlueScoreW == 0 {
BlueScoreW = 999
WatchBlueScoreLabel.setText("999")
}
else {
BlueScoreW -= 1
WatchBlueScoreLabel.setText(String(BlueScoreW))
}
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
WatchRedScoreLabel.setText(String(RedScoreW))
WatchBlueScoreLabel.setText(String(BlueScoreW))
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
And this is the Destination View Controller where I want to be able to use RedScoreW and BlueScoreW variables.
class InterfaceController3: WKInterfaceController {
#IBOutlet var finalRedScoreLabel: WKInterfaceLabel!
#IBOutlet var finalBlueScoreLabel: WKInterfaceLabel!
#IBAction func DoneAndResetButton() {
self.popToRootController()
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
* EDIT *
I am trying to do it this way, this is the code where I send it, check:
#IBAction func FinishButtonPushVariables() {
arrayofScores[0] = RedScoreW
arrayofScores[1] = BlueScoreW
pushControllerWithName("LastScreen", context: arrayofScores)
}
And this is where I receive it... and it doesn't work. LOL
#IBOutlet var finalRedScoreLabel: WKInterfaceLabel!
#IBOutlet var finalBlueScoreLabel: WKInterfaceLabel!
#IBAction func DoneAndResetButton() {
self.popToRootController()
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let finalarrayofScores = context as? InterfaceController2
finalBlueScoreLabel.setText(String(finalarrayofScores!.arrayofScores[1]))
finalRedScoreLabel.setText(String(finalarrayofScores!.arrayofScores[0]))
// Configure interface objects here.
}
In iOS apps, we use prepareForSegue to do this. On watchOS apps, we use contextForSegueWithIdentifier to pass a context from one interfaceController to another.
Here is a link to the class reference that will detail more about this. But here are the basics:
There are two different methods that can be used. One is for going from one interface controller to another:
func contextForSegueWithIdentifier(_ segueIdentifier: String) -> AnyObject?
The other is for going from a one interface controller to another when a row in a table is tapped:
func contextForSegueWithIdentifier(_ segueIdentifier: String, inTable table: WKInterfaceTable, rowIndex rowIndex: Int) -> AnyObject?
So one of these two methods will go in the interfaceController that is sending the context, and you will receive that context in the awakeWithContext method of the receiving interfaceController.
Here is a link to a tutorial that will show an application of this process.
EDIT
Here is a specific solution to your problem.
In the interface controller where you send it, put this code:
override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
arrayofScores[0] = RedScoreW
arrayofScores[1] = BlueScoreW
return arrayOfScores
}
Then in your destination interface controller, put this code:
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let finalArrayOfScores = context as? [Int]
if let f = finalArrayOfScores {
finalBlueScoreLabel.setText(String(f[1]))
finalRedScoreLabel.setText(String(f[0]))
}
}
You need to set up variables to hold your variable first.
class YourSecondViewController: UIViewController {
var yourVariable:Double?
}
Then have your button trigger your custom segue. Use your variable as the argument for sender.
class YourFirstViewController: UIViewController {
#IBAction func buttonTapped(sender: AnyObject) {
self.performSegueWithIdentifier("segue", sender: yourVariable)
}
}
Then pass the sender data by overriding the prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier = "segue") {
let secondViewController = segue.destinationViewController as YourSecondViewController
let yourVariable = sender as Double
secondViewController.duration = yourVariable
}
}
I guess your problem is that you are passing an array to the context and you cast it as WKIntefaceController.
Try replacing this line
let finalarrayofScores = context as? InterfaceController2
by
let finalarrayofScores = context as? [Int]