How can I set up Amazon Amplify iOS in Objective-C? - ios

The docs show Swift code only. When trying to use Objective-C, I can't access any of the Amplify libraries. Am I missing an installation step?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
do {
try Amplify.add(plugin: AWSCognitoAuthPlugin())
try Amplify.add(plugin: AWSPinpointAnalyticsPlugin())
try Amplify.configure()
print("Amplify configured with Auth and Analytics plugins")
} catch {
print("Failed to initialize Amplify with \(error)")
}
return true
}
How to do the equivalent in Objective-C?

Yes, crusty old Objective C apps still exist. And they have to be maintained. It really isn't reasonable to expect developers to rewrite them in Swift just so they can use Amplify. I was recently asked to add Amplify to an app built in early 2015 before Swift existed. (Gosh -- are there really apps out there that are over 5 years old?!)
Fortunately, if you can bite the bullet and add Swift support to your Objective C project, it isn't so hard to make a Swift wrapper class that you use from Objective C. Here's one I created for free. It would be nice if the highly paid folks at Amazon would be kind enough to help us out with examples like this.
import Amplify
import AmplifyPlugins
#objc
class AmplifyWrapper: NSObject {
override init() {
super.init()
}
#objc
public func initialize() {
do {
try Amplify.add(plugin: AWSCognitoAuthPlugin())
try Amplify.add(plugin: AWSPinpointAnalyticsPlugin())
try Amplify.configure()
print("Amplify configured with Auth and Analytics plugins")
} catch {
print("Failed to initialize Amplify with \(error)")
}
}
#objc
public func recordEvent(name: String, category: String, accountId: String) {
let properties: AnalyticsProperties = [ "category": category, "accountId": accountId]
let event = BasicAnalyticsEvent(name: name, properties: properties)
Amplify.Analytics.record(event: event)
}
}
Use form Objective C like this:
#import "PutYourAppNameHere-Swift.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[[AmplifyWrapper alloc] init] initialize];
...
}
Then later on you can do this:
[[[AmplifyWrapper alloc] init] recordEventWithName:#"App Opened" category:#"Counts" accountId:""];

Am I missing an installation step?
You're not missing anything. Unfortunately there's no Objective-C support on Amplify for iOS. Amplify was built in Swift using its full capabilities and Objective-C support is not currently being considered unless there's a strong demand from the community.
Out of curiosity: are you starting a new app in Objective-C? If so I would be curious to understand why not going with Swift given Apple's investment on Swift lately (Combine, SwiftUI, Swift language updates, etc).
If you're trying to integrate Amplify in an existing Objective-C app, then I'm afraid it won't be possible.

Related

OAuthSwift SceneDelegate.swift issue ObjC+Swift project

OAuthSwift(2.2.0) is being added in exisitng iOS OBJC project. The project was developed for iOS version 8 and earlier. For updating the project, we are not taking up full conversion of the project to swift. Decided to retain core ObjC modules, and develop all new features using Swift, for iOS 13.0. More importantly, the project also uses C++ libraries and other 3rd party libraries.
The project has 100+ implementation classes had each have 100s of lines of code. I understands from several guides and articles that there are lot of hiccups in OBJC > SWIFT conversion.
The existing AppDelegate.m has interoperability with several controllers, we are currently held with didFinishLaunchingWithOptions between AppDelegate.m and SceneDelgate.swift.
The actual issue:
we tried,
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else {
return
}
if url.host == "oauth-callback" {
OAuthSwift.handle(url: url)
}
}
But, Scene mapping causing issues with UIWindow nil, unable to present the rootviewcontroller.
Optionally, I have tried, openURL in AppDelegate.m as below.
(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
if ([url.host isEqualToString:#"oauth-callback"]) {
[OAuthSwift handleWithUrl:url];
}
return YES;
}
The above seems to be accessing the OAuthToken. However, OAuthToken is getting lost somewhere. It would be guiding to know....
Whether our strategy to retain Objc feasible?
How do we solve SceneDelegate.swift and AppDelegate.m interoperability issues, with OAuth? Any reference to OBJC compatible OAuth2.0 Framework?
Can SFSafariController be used, instead to call OAuthSwift, access token?
Thanks in advance.
In OBJC project, using interoperability feature which is available between SWIFT and OBJC code, this url
if ([url.host isEqualToString:#"oauth-callback"]) {
[OAuthSwift handleWithUrl:url];
}
Declaring in header
#class OAuthSwift;
Declaring #objc in Swift
#objc open class func handle(url: URL) {
let notification = Notification(name: OAuthSwift.didHandleCallbackURL, object: nil,
userInfo: [CallbackNotification.optionsURLKey: url])
notificationCenter.post(notification)
}
Used SFSafariViewControllerDelegate.
This implementation, did not insist for SceneDelegate with 'OAuthSwift', '~> 2.2.0'. Interoperability works fine.

Nearby Bluetooth devices using Swift 3.0

I'm looking for a way to programmatically list any nearby Bluetooth devices (discoverable) that my device finds. I have not been able to find any information or tutorials regarding performing this call in Swift 3.0. This Q-A post discusses finding these devices using Swift 1.0 and building in Xcode 6, rather than the latest version 8.
I did my best to try to make my code into the 3.0 Syntax from the 1.0, but while running the following code, nothing is returned in the Playground:
import Cocoa
import IOBluetooth
import PlaygroundSupport
class BlueDelegate : IOBluetoothDeviceInquiryDelegate {
func deviceInquiryComplete(_ sender: IOBluetoothDeviceInquiry, error: IOReturn, aborted: Bool) {
aborted
print("called")
let devices = sender.foundDevices()
for device : Any? in devices! {
if let thingy = device as? IOBluetoothDevice {
thingy.getAddress()
}
}
}
}
var delegate = BlueDelegate()
var inquiry = IOBluetoothDeviceInquiry(delegate: delegate)
inquiry?.start()
PlaygroundPage.current.needsIndefiniteExecution = true
Using IOBluetooth the Correct Way
The following code works flawlessly in Xcode Version 8.2.1 (8C1002), Swift 3.0. There are a few lines that aren't required, such as the entire method of deviceInquiryStarted.
Update: These usages still work as of Xcode 9.2 (9B55) and Swift 4.
Playground
import Cocoa
import IOBluetooth
import PlaygroundSupport
class BlueDelegate : IOBluetoothDeviceInquiryDelegate {
func deviceInquiryStarted(_ sender: IOBluetoothDeviceInquiry) {
print("Inquiry Started...")
//optional, but can notify you when the inquiry has started.
}
func deviceInquiryDeviceFound(_ sender: IOBluetoothDeviceInquiry, device: IOBluetoothDevice) {
print("\(device.addressString!)")
}
func deviceInquiryComplete(_ sender: IOBluetoothDeviceInquiry!, error: IOReturn, aborted: Bool) {
//optional, but can notify you once the inquiry is completed.
}
}
var delegate = BlueDelegate()
var ibdi = IOBluetoothDeviceInquiry(delegate: delegate)
ibdi?.updateNewDeviceNames = true
ibdi?.start()
PlaygroundPage.current.needsIndefiniteExecution = true
Project-Application Usage
import Cocoa
import IOBluetooth
import ...
class BlueDelegate : IOBluetoothDeviceInquiryDelegate {
func deviceInquiryStarted(_ sender: IOBluetoothDeviceInquiry) {
print("Inquiry Started...")
}
func deviceInquiryDeviceFound(_ sender: IOBluetoothDeviceInquiry, device: IOBluetoothDevice) {
print("\(device.addressString!)")
}
}
//other classes here:
//reference the following outside of any class:
var delegate = BlueDelegate()
var ibdi = IOBluetoothDeviceInquiry(delegate: delegate)
//refer to these specifically inside of any class:
ibdi?.updateNewDeviceNames = true
ibdi?.start() //recommended under after an action-button press.
Explanation
The issue I was originally faced with was trying to access the information as the inquiry was still in process.
When I accessed it, under many different occasions my playground would hang and I would be forced to force quit both Xcode.app, and com.apple.CoreSimulator.CoreSimulatorService from the Activity Monitor. I lead myself to believe that this was just a Playground bug, only to learn that my application would crash once the inquiry finished.
As Apple's API Reference states:
Important Note: DO NOT perform remote name requests on devices from delegate methods or while this object is in use. If you wish to do your own remote name requests on devices, do them after you have stopped this object. If you do not heed this warning, you could potentially deadlock your process.
Which entirely explained my issue. Rather than directly asking for the IOBluetoothDevice information from the sender.foundDevices() method (which I believe may not have been updating..?) I simply used the parameters built into the function to mention that it was indeed an IOBluetoothDevice object, and simply to ask for that information to be printed.
Final Note
I hope that this Q/A I've created helps others in need when using IOBluetooth in Swift. The lack of any tutorials and the high amounts of outdated, Objective-C code made finding this information very challenging. I'd like to thank #RobNapier for the support on trying to find the answer to this riddle in the beginning. I'd also like to thank NotMyName for the reply on my post on the Apple Developer Forums.
I will be exploring the usage of this in an iOS device more sooner than later!

Quickblox Webrtc Video Calling Swift - Documentation understanding

I am trying to integrate the Quickblox Webrtc Video Calling feature into a iOS Swift App. However, i'm having a lot of trouble with their SDK & api documentation, and it seems they don't have a tech team to help people with questions about their platforms, so maybe we can all help each other, so here are a few questions that I've noticed a lot of people have been asking on both StackOverFlow and Github regarding their webrtc SDK. Please restrict answers to the Swift language. The docs link is
http://quickblox.com/developers/SimpleSample-videochat-ios
My code so far:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
//Firebase config
FIRApp.configure()
//Quickblox config
QBSettings.setApplicationID(xxxxx)
QBSettings.setAuthKey("xxxxxxxxxxx")
QBSettings.setAuthSecret("xxxxxxxx-xxxx")
QBSettings.setAccountKey("xxxxxxxxxxxxxxxxxxxx")
return true
}
Thats my appdelegate.swift now for the part that giving me problems the actual videochatviewcontroller. The documentation is very vague at the start all is says is:
// Initialize QuickbloxWebRTC and configure signaling
// You should call this method before any interact with
QuickbloxWebRTC QBRTCClient.initializeRTC() // Call this method when
you finish your work with QuickbloxWebRTC
QBRTCClient.deinitializeRTC()
I do not know if I am to call this in my appdelegate.swift or if I should call this in VideoChatViewController's viewDidLoad method or should I create a new method altogether?
Secondly,the docs say to CALL USERS use this method, but its not a method, just random variables, also it doesn't tell tell whether it goes to the viewDidLoad or to a newly created method :
QBRTCClient.instance().addDelegate(self) // self class must conform to QBRTCClientDelegate protocol
// 2123, 2123, 3122 - opponent's
let opponentsIDs = [3245, 2123, 3122]
let newSession = QBRTCClient.instance().createNewSessionWithOpponents(opponentsIDs, withConferenceType: QBRTCConferenceType.Video)
// userInfo - the custom user information dictionary for the call. May be nil.
let userInfo :[String:String] = ["key":"value"]
newSession.startCall(userInfo)
Next, they are vague regarding the method to receive a new session, below they refer to self.session which they never explain where this variable is from or what it consist of
func didReceiveNewSession(session: QBRTCSession!, userInfo: [NSObject : AnyObject]!) {
if self.session != nil {
// we already have a video/audio call session, so we reject another one
// userInfo - the custom user information dictionary for the call from caller. May be nil.
let userInfo :[String:String] = ["key":"value"]
session.rejectCall(userInfo)
}
else {
self.session = session
}
}
Does quickblox require authenticated Quickblox users to use their webrtc or can I Authenticate users with Firebase or parse?
Where do I use QBRTCConfig in the appdelegate or the viewDidLoad? I have tried both and have seen it used in both methods.

Is it possible to stub HTTP requests in Xcode 7 automated UI tests?

I've been trying to intercept and stub/mock HTTP requests in Xcode 7 automated UI tests, using tools like OHHTTPStubs, with no luck.
Here's an example of how I am trying to capture any HTTP request using OHHTTPStubs in the setUp method of a UI test file:
override func setUp() {
super.setUp()
let matcher: OHHTTPStubsTestBlock = { (request) -> Bool in
return true
}
OHHTTPStubs.stubRequestsPassingTest(matcher) { (response) -> OHHTTPStubsResponse! in
return OHHTTPStubsResponse.init()
}
}
Is there something about the way that UI testing works that prevents this? has anyone been able to achieve this?
As Martijn correctly pointed out, because of how UI tests work, you cannot directly interact with the app at runtime, so any HTTP mocking or manipulation of things like NSUserDefaults in a XCUITestCase will not affect your app.
If you really need to be able to mock HTTP or setup & teardown your apps environment for specific UI tests, you will need to set launch arguments or launch environment variables before launching the app in the setUp() method of a XCUITestCase and then modify your app code to read the launch arguments or environment variables and bootstrap the test environment.
Example TestCase
class MyTestCase: XCTestCase {
/**
Called before each test in this test case.
*/
override func setUp() {
super.setUp()
let app = XCUIApplication()
app.launchArguments = [ "STUB_HTTP_ENDPOINTS" ]
app.launch()
}
}
Example AppDelegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
#if DEBUG
if (NSProcessInfo.processInfo().arguments.contains("STUB_HTTP_ENDPOINTS")) {
// setup HTTP stubs for tests
}
#endif
return true
}
Note: In order to use an HTTP mocking framework like OHHTTPStubs in this example, the stubbing code and any JSON fixtures you need to use will all need to be in your app target, not the test target.
This is a very useful thread to read on the topic: https://github.com/AliSoftware/OHHTTPStubs/issues/124
UI tests are ran in a separate instance from your application. While the classes from the application might be available to you, they are merely a copy.
In your application you can detect if you're running in UI testing mode with solutions provided here: How to detect if iOS app is running in UI Testing mode
I personally went with the launchEnvironment solution mentioned in the original post; my setUp looks like this:
override func setUp() {
super.setUp()
let app = XCUIApplication()
app.launchEnvironment["TEST"] = "1"
app.launch()
}
And one of my singleton instantiators (called RealmManager) looks like this (for instantiating a Realm database):
func realm() -> Realm {
let dic = NSProcessInfo.processInfo().environment
if dic["TEST"] != nil {
return try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "test"))
}
return try! Realm()
}
If you dislike the duplication, but you're probably already duplicating XCUIApplication().launch() anyway, you can always make a custom test case class that extend XCTestCase, override the setUp there with this addition and then use that in all your test classes.

Can I disable custom keyboards (iOS8) for my app?

EDIT: tl;dr - it is possible, see accepted answer below.
Is there any (not only programatic) way of preventing custom keyboards (iOS8) from being used for my application? I am mainly interested in "per-app" setting, so just my app is not allowed to use custom keyboards, but disabling custom keyboards system-wide is last resort.
So far I know that custom keyboards are system-wide and can be used by any application. The OS will fallback to stock keyboard only for secure text entry (text fields with secureTextEntry set to YES). Not much hope here.
I got an impression from App Extension Programming Guide that MDM (Mobile Device Management) can restrict device from using custom keyboards at all, but I didn't find that option in the new beta version of Apple Configurator.app for OS X Yosemite. Is 'Configurator' just missing that option?
Any ideas here? Should I file a radar to suggest that Apple should introduce such functionality?
Looks like you got what you wanted in beta seed 3. Line 440 of UIApplication.h:
// Applications may reject specific types of extensions based on the extension point identifier.
// Constants representing common extension point identifiers are provided further down.
// If unimplemented, the default behavior is to allow the extension point identifier.
- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(NSString *)extensionPointIdentifier NS_AVAILABLE_IOS(8_0);
It's not currently included in the docs, but sound like it will do exactly what you asked here.
I'm guessing these "extension point identifiers" are not unique identifiers of extensions, but of their types, as there is also this on line 545:
// Extension point identifier constants
UIKIT_EXTERN NSString *const UIApplicationKeyboardExtensionPointIdentifier NS_AVAILABLE_IOS(8_0);
TLDR: to disable custom keyboards you would include something like this in your app delegate:
- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(NSString *)extensionPointIdentifier {
if ([extensionPointIdentifier isEqualToString: UIApplicationKeyboardExtensionPointIdentifier]) {
return NO;
}
return YES;
}
Swift 3 :
func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplicationExtensionPointIdentifier) -> Bool {
if extensionPointIdentifier == UIApplicationExtensionPointIdentifier.keyboard {
return false
}
return true
}
I just want to add this for those developers who want to implement this method in Xamarin iOS. The idea is to override theShouldAllowExtensionPointIdentifier method in your AppDelegate:
public override bool ShouldAllowExtensionPointIdentifier(UIApplication application, NSString extensionPointIdentifier)
{
if (extensionPointIdentifier == UIExtensionPointIdentifier.Keyboard)
{
return false;
}
return true;
}
In Swift 5, UIApplicationExtensionPointIdentifier was changed to UIApplication.ExtensionPointIdentifier.
func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier) -> Bool {
if extensionPointIdentifier == UIApplication.ExtensionPointIdentifier.keyboard {
return false
}
return true
}
In Swift 5 using a switch:
func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier) -> Bool {
switch extensionPointIdentifier {
case .keyboard:
return false
default:
return true
}
}

Resources