Converting Swift convenience initializer to Objective-C - ios

I need to convert the following convenience initializer from Swift to Objective-C
convenience init(view: UIView, overlapHandler: (CGRect)->()) {
self.init(view: view, handler: { overlap -> () in
overlapHandler(overlap ?? CGRect.nullRect)
})
}
My first attempt was:
-(instancetype) initWithView:(UIView*)view overlapHandler:(Handler)handler
{
Handler overlapHandler = ^(CGRect overlap) {
if (CGRectIsNull(overlap)) {
return;
}
handler(overlap);
};
return [self initWithView:view andHandler:overlapHandler];
}
But it didn't work well as I don't see how an Objective-C block can receive a non-optional CGRect.
The context of the code is below:
import UIKit
class KeyboardOverlapNotifier:NSObject {
typealias Handler = (CGRect?) -> ()
let view:UIView
let handler:Handler
init(view:UIView, handler:Handler) {
self.view = view
self.handler = handler
super.init()
beginListening()
}
deinit {
stopListening()
}
func beginListening() {
token = token ?? nc.addObserverForName(UIKeyboardWillChangeFrameNotification,
object:nil, queue:nil, usingBlock:keyboardFrameDidChange)
}
func stopListening() {
if let observer = token {
nc.removeObserver(observer)
token = nil
}
}
private let nc:NSNotificationCenter = NSNotificationCenter.defaultCenter()
private var token:NSObjectProtocol?
}
private extension KeyboardOverlapNotifier {
func keyboardFrameDidChange(notification: NSNotification!) {
let duration = notification.userInfo!
[UIKeyboardAnimationDurationUserInfoKey] as NSTimeInterval
UIView.animateWithDuration(duration,
delay: 0,
options: .BeginFromCurrentState,
animations: {
let keyboard:CGRect = {
let global = (notification.userInfo!
[UIKeyboardFrameEndUserInfoKey] as NSValue).CGRectValue()
let local = self.view.convertRect(global, fromView: nil)
return local
}()
let overlap = self.view.bounds.rectByIntersecting(keyboard)
self.handler(overlap.nonEmptyOrNil)
},
completion: nil)
}
}
extension KeyboardOverlapNotifier {
convenience init(view: UIView, overlapHandler: (CGRect)->()) {
self.init(view: view, handler: { overlap -> () in
overlapHandler(overlap ?? CGRect.nullRect)
})
}
}
extension CGRect {
var nonEmptyOrNil:CGRect? { return !self.isEmpty ? self : nil }
}

Perhaps this should help? I'm still unsure what your question actually is so the only thing I can offer is references to each.
Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages.
Closures can capture and store references to any constants and variables from the context in which they are defined. This is known as closing over those constants and variables, hence the name “closures”. Swift handles all of the memory management of capturing for you.
Blocks vs Closures
https://www.codefellows.org/blog/writing-completion-blocks-with-closures-in-swift
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html

Related

Selector not being called inside of closure

I'm calling a selector inside of a closure, but the selector won't trigger inside of the closure but outside of it works fine.
The closure functions "scrollAllFaces" works as expected
Could it be that #selector() can't work inside of closures?
Any way of making it work or workaround?
override class var defaultTestSuite: XCTestSuite {
let testSuite = XCTestSuite(forTestCaseClass: ClockFaceBridgeTests.self)
ClockFaceBridgeTests().app.launch()
ClockFaceBridgeTests().scrollAllFaces { sectionTitle, currentFace in
testSuite.addTest(ClockFaceBridgeTests(selector: #selector( ClockFaceBridgeTests().testAllFacesAndNewFacesAvailable )))
}
return testSuite
}
The "scrollAllFaces" function:
func scrollAllFaces(untilSectionIndex: Int = BridgeApplication.facesCaterogies.count, completion: #escaping (String, XCUIElement) -> Void) {
try? app.stimulateDataFetching()
var currentSectionIndex = 0
while currentSectionIndex < untilSectionIndex - 1 {
let section = BridgeFaceGalleryView.shared.table.cells.element(boundBy: currentSectionIndex)
let sectionTitle = section.staticTexts.firstMatch.label.description
guard sectionTitle != "Photos" ||
FaceDetailsView.shared.parentApp.staticTexts["You must choose photos."].doesNotExist else {
return
}
section.scrollToVisible()
for index in 0..<section.cells.count {
let currentCell = section.cells.element(boundBy: index)
currentCell.scrollToVisible()
completion(sectionTitle, currentCell)
}
currentSectionIndex += 1
}
}
The "testAllFacesAndNewFacesAvailable" function:
#objc func testAllFacesAndNewFacesAvailable() {
print("Function called")
}

Most efficient way of dealing with multiple functions that have the same signature but different parameters

Currently I have multiple functions that have the same signature but do different things when called base:
func drawPath(from: JMapDestination, to: JMapDestination) {
guard let fromWaypoint = navigationManager.getWaypointForDestination(destination: from),
let toWaypoint = navigationManager.getWaypointForDestination(destination: to) else {
return
}
drawPath(from: fromWaypoint, to: toWaypoint)
}
func drawPathFrom(_ from: CGPoint, to: JMapDestination) {
guard let fromWaypoint = getWaypointForPoint(point: from),
let toWaypoint = navigationManager.getWaypointForDestination(destination: to) else {
return
}
drawPath(from: fromWaypoint, to: toWaypoint)
}
func drawPath(from: CGPoint, to: JMapWaypoint) {
guard let fromWaypoint = getWaypointForPoint(point: from) else { return }
drawPath(from: fromWaypoint, to: to)
}
I decide to create an enum and one master function using switch statements to handle the different cases:
enum pathType {
case jMapWaypoint
case jMapDestination
case cgPoint
}
func drawPath(pathType: pathType, fromJMap: JMapWaypoint?, toJMap: JMapWaypoint?, fromJDestination: JMapDestination?, toJDestination: JMapDestination?, fromCGPoint: CGPoint?) {
switch pathType {
case .jMapWaypoint:
guard let mapController = viewModel?.mapController else {
return
}
let pathStyle = setPathStyle(style: JMapStyle.init())
if let from = fromJMap, let to = toJMap {
checkPathsBetweenWaypoints(mapController: mapController, pathStyle: pathStyle, from: from, to: to)
}
if let currentMap = mapController.currentMap {
mapController.zoomToPath(on: currentMap, withPadding: 100, withAnimationDuration: 1)
}
case .jMapDestination:
if let from = fromJDestination, let to = toJDestination {
guard let fromWaypoint = getWaypointForDestination(destination: from),
let toWaypoint = getWaypointForDestination(destination: to) else {
return
}
drawPath(pathType: .jMapWaypoint, fromJMap: fromWaypoint, toJMap: toWaypoint, fromJDestination: nil, toJDestination: nil, fromCGPoint: nil)
}
case .cgPoint:
if let from = fromCGPoint, let to = toJMap {
guard let fromWaypoint = getWaypointForPoint(point: from) else { return }
drawPath(pathType: .jMapWaypoint, fromJMap: fromWaypoint, toJMap: to, fromJDestination: nil, toJDestination: nil, fromCGPoint: nil)
}
}
}
the function with the switch statement is working but I'm wondering if there is a cleaner, more efficient way of doing this? FYI all the functions are on the same viewController, I was thinking protocols but how would be able to do it if the protocol function signature is the same (ie drawPath) but with different parameter?
A cleaner version (IMHO) would be an struct containing your path beginning and end, like:
struct MyPath {
var start: JMapWaypoint
var end: JMapWaypoint
init(start: CGPoint, end: CGPoint)
{
//Your logic here
}
init(start: JMapWaypoint, end: JMapWaypoint)
{
self.start = start
self.end = end
}
init(start: JMapDestination, end: JMapDestination)
{
//Your logic here
}
}
Then you can simply init this object with whichever type you want, get the final desired type and draw your path with this object instance.
I don't know if its the right way ... for a matter of convenience what I would have done is created extensions for CGPoint and JMapDestination which would have a method of converting this to JMapWaypoint called func asWaypoint. Finally I would use the extension method to just call the final method rather than jumping through so many hoops.
Example:
extension CGPoint {
func asWaypoint() -> JMapWaypoint {
// your logic for the conversion
}
}
And then just call the final method as
drawPath(from: from.asWaypoint(), to: to.asWaypoint())
Hope this helps.

How to find printers on IOS from Brother SDK?

I find printers from iPad setting print can find my Brother printer.
But when I try the code I get empty device list and I don't know why.
I am not familiar with Swift. I just try the sample code from Official documentation.
https://support.brother.com/g/s/es/htmldoc/mobilesdk/guide/discover-printer.html
Here is my code:
func getPrinter() {
let printerManager = BRPtouchNetworkManager()
printerManager.setPrinterName("Brother QL-720NW")
printerManager.startSearch(5)
printerManager.getPrinterNetInfo()
print("start")
let testFind = YourClass()
print("1")
testFind.startSearchWiFiPrinter()
testFind.didFinishSearch(printerManager)
print("2")
}
class YourClass: NSObject, BRPtouchNetworkDelegate {
private var networkManager: BRPtouchNetworkManager?
func startSearchWiFiPrinter() {
print("3")
let manager = BRPtouchNetworkManager()
manager.delegate = self
manager.startSearch(5)
self.networkManager = manager
}
// BRPtouchNetworkDelegate
func didFinishSearch(_ sender: Any!) {
print("4")
guard let manager = sender as? BRPtouchNetworkManager else {
print("5")
return
}
guard let devices = manager.getPrinterNetInfo() else {
print("6")
return
}
print(devices)
print("7")
for deviceInfo in devices {
print("8")
if let deviceInfo = deviceInfo as? BRPtouchDeviceInfo {
print("Model: \(deviceInfo.strModelName), IP Address: \(deviceInfo.strIPAddress)")
}
}
}
}
I call my function getPrinter() and here is my print log:
The SDK documentation gives you an example implementation of two methods:
func startSearchWiFiPrinter() {}
and
func didFinishSearch(_ sender: Any!) {}
In the class you want to execute the search you must implement these. You also need to declare the class to attend the protocol BRPtouchNetworkDelegate. The last thing is to have a property to be able to hold the Network manager (Which is done in the line: private var networkManager: BRPtouchNetworkManager?)
However, you are not supposed to call the "didFinishSearch" method by yourself. When you call startSearchWiFiPrinter, the search begins, and the BRPtouchNetworkManager instance itself calls the didFinishSearch method. It is capable of doing so because you set the delegate in the line: manager.delegate = self.
You should not need 2 classes for this. You should not use 2 instances of BRPtouchNetworkManager either.
Try this. Remember the number you put as an argument to startSearchWiFiPrinter means how long in seconds the search will be.
class EXAMPLEClass: NSObject, BRPtouchNetworkDelegate {
private var networkManager: BRPtouchNetworkManager?
func getPrinter() {
self.startSearchWiFiPrinter()
}
func startSearchWiFiPrinter() {
let manager = BRPtouchNetworkManager()
manager.delegate = self
manager.setPrinterName("Brother QL-720NW")
manager.startSearch(5)
self.networkManager = manager
}
// BRPtouchNetworkDelegate
func didFinishSearch(_ sender: Any!) {
print("4")
guard let manager = sender as? BRPtouchNetworkManager else {
print("5")
return
}
guard let devices = manager.getPrinterNetInfo() else {
print("6")
return
}
print(devices)
print("7")
for deviceInfo in devices {
print("8")
if let deviceInfo = deviceInfo as? BRPtouchDeviceInfo {
print("Model: \(deviceInfo.strModelName), IP Address: \(deviceInfo.strIPAddress)")
}
}
}
}

Remove the observer using the handle in Firebase in Swift

I have the following case. The root controller is UITabViewController. There is a ProfileViewController, in it I make an observer that users started to be friends (and then the screen functions change). ProfileViewController can be opened with 4 tabs out of 5, and so the current user can open the screen with the same user in four places. In previous versions, when ProfileViewController opened in one place, I deleted the observer in deinit and did the deletion just by ref.removeAllObservers(), now when the user case is such, I started using handle and delete observer in viewDidDisappear. I would like to demonstrate the code to find out whether it can be improved and whether I'm doing it right in this situation.
I call this function in viewWillAppear
fileprivate func firObserve(_ isObserve: Bool) {
guard let _user = user else { return }
FIRFriendsDatabaseManager.shared.observeSpecificUserFriendshipStart(observer: self, isObserve: isObserve, userID: _user.id, success: { [weak self] (friendModel) in
}) { (error) in
}
}
This is in the FIRFriendsDatabaseManager
fileprivate var observeSpecificUserFriendshipStartDict = [AnyHashable : UInt]()
func observeSpecificUserFriendshipStart(observer: Any, isObserve: Bool, userID: String, success: ((_ friendModel: FriendModel) -> Void)?, fail: ((_ error: Error) -> Void)?) {
let realmManager = RealmManager()
guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
DispatchQueue.global(qos: .background).async {
let specificUserFriendRef = Database.database().reference().child(MainGateways.friends.description).child(currentUserID).child(SubGateways.userFriends.description).queryOrdered(byChild: "friendID").queryEqual(toValue: userID)
if !isObserve {
guard let observerHashable = observer as? AnyHashable else { return }
if let handle = self.observeSpecificUserFriendshipStartDict[observerHashable] {
self.observeSpecificUserFriendshipStartDict[observerHashable] = nil
specificUserFriendRef.removeObserver(withHandle: handle)
debugPrint("removed handle", handle)
}
return
}
var handle: UInt = 0
handle = specificUserFriendRef.observe(.childAdded, with: { (snapshot) in
if snapshot.value is NSNull {
return
}
guard let dict = snapshot.value as? [String : Any] else { return }
guard let friendModel = Mapper<FriendModel>().map(JSON: dict) else { return }
if friendModel.friendID == userID {
success?(friendModel)
}
}, withCancel: { (error) in
fail?(error)
})
guard let observerHashable = observer as? AnyHashable else { return }
self.observeSpecificUserFriendshipStartDict[observerHashable] = handle
}
}
Concerning your implementation of maintaining a reference to each viewController, I would consider moving the logic to an extension of the viewController itself.
And if you'd like to avoid calling ref.removeAllObservers() like you were previously, and assuming that there is just one of these listeners per viewController. I'd make the listener ref a variable on the view controller.
This way everything is contained to just the viewController. It also is potentially a good candidate for creating a protocol if other types of viewControllers will be doing similar types of management of listeners.

Swift: Array of base class does not call subclass function implementation

I'm trying to inject a fake instance into a unit test for a class depending on SimplePing, a NSObject subclass. My class has a property var simplePings: [SimplePing] which in my unit test I set as an array of FakeSimplePing. However, when the class goes in the array and calls simplePing.start(), it calls the SimplePing.start implementation instead of FakeSimplePing's, even though when I debug I see that the instance type is FakeSimplePing.
When the property is just a single SimplePing, the unit test uses the FakeSimplePing.start and the test passes. Does this have something to do with Swift and arrays of superclasses?
class Pinger : NSObject {
private var simplePings: [SimplePing] = []
func pingLocation(location: Location) -> Signal<Double, NoError> {
let simplePings = location.serverIPs.map { (serverIP: String) -> SimplePing in
let simplePing = SimplePing(hostName: serverIP)
simplePing?.delegate = self
return simplePing
}
configureDependencies(simplePings)
simplePings.forEach { $0.start() }
return signal
}
func configureDependencies(simplePings: [SimplePing]) {
if self.simplePings.isEmpty {
self.simplePings = simplePings
}
}
}
class FakeSimplePing: SimplePing {
var receivedStart = false
var receivedSendPingWithData = false
var fakeHostName: String!
override var hostName: String {
return fakeHostName
}
convenience init(hostName: String) {
self.init()
fakeHostName = hostName
}
override func start() {
// This does not get called
receivedStart = true
delegate?.simplePing?(self, didStartWithAddress: nil)
delegate?.simplePing?(self, didReceivePingResponsePacket: nil)
}
override func sendPingWithData(data: NSData!) {
receivedSendPingWithData = true
}
}
And the failing test:
beforeEach {
fakeSimplePing = FakeSimplePing(hostName: serverIP)
fakeSimplePing.delegate = pinger
pinger.configureDependencies([fakeSimplePing])
}
it("pings server with data") {
pinger.pingLocation(location)
expect(fakeSimplePing.receivedSendPingWithData).toEventually(beTrue())
}
The problem (I believe...) is in the naming in pingLocation
in the line
let simplePings = location.serverIPs.map { ....
you use the same name as of your property
private var simplePings: [SimplePing] = []
so you may think you're defining a new variable with the let, but actually, you may just use your property and change it on the way, so it got changed to SimplePing array, as it returns from the map
try to change your method into:
func pingLocation(location: Location) -> Signal<Double, NoError> {
let tempSimplePings = location.serverIPs.map { (serverIP: String) -> SimplePing in
let simplePing = SimplePing(hostName: serverIP)
simplePing?.delegate = self
return simplePing
}
configureDependencies(tempSimplePings)
simplePings.forEach { $0.start() }
return signal
}

Resources