Best practice for presenting App internal error to user? - ios

I use guard statement and fatalError() a lot in my app to make sure data are in consistent state. They help to catch bugs in development phase. Now I'm in the late phase of the project and start to consider how to deal with those fatalError() calls in release build.
I don't want to remove them because they help to expose unknown bugs. I don't want to just leave them as is in the product release either because they would just abort the App, which doesn't provide user any helpful information about what went wrong. What I'd like to achieve is to show an error message on the screen and then abort when user press "OK". I think there may be two approaches to do it:
1) Don't call fatalError(). Throw error instead. Let the top level code handles the error (e.g., showing an alert). The issue with the approach is that it requires to change many functions to become throwable, which I think is inconvenient.
2) The second approach is that from what I read on the net, it's possible for code to create alert without access to the current view controller on screen. The trick is to create a new window. I haven't investigated the details yet.
My concern is that I think both approaches have same inherent limitation and are not applicable in all situations. For example, suppose there is something goes wrong in a UITableViewControler's data source delegate method, does it work to present an alert from within the delegate method? I doubt it.
So I wonder what's the common practice to present the fatal error message to user? Thanks for any suggestions.

similar to create a window, there is a way to get 'currentViewController', you can use it to show an alert anywhere.
{
let view = UIViewController.current.view
Alert.show(on: view, message: errorMsg)
//or just: Alert.show(error), handle it in Alert class
}
extension UIViewController {
class func current(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return current(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
return current(base: tab.selectedViewController)
}
if let presented = base?.presentedViewController {
return current(base: presented)
}
return base
}
}
for UITableView/UIScrollView/UICollectionView, you can use runtime swizzle method to add a placeholder image when there is no data or an error occored for all views. such as EmptyDataSet
recored the errors and save the log into a local file, upload it to your server if necessary, analyse them and help users to solve there problem.

Related

firestore collection path giving bugs with constants value and String value

So my goal is to get rid of these bugs completely. I am in a dilemma where each decision leads to a bug.
The first thing I can do that eventually becomes an issue is use a String-interpolated collection path in all my query functions like so:
func getEventName() {
listener = db.collection("school_users/\(user?.uid)/events").order(by: "time_created", descending: true).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
if let error = error {
print("There was an error fetching the data: \(error)")
} else {
self.events = querySnapshot!.documents.map { document in
return EventName(eventName: (document.get("event_name") as! String))
}
self.tableView.reloadData()
}
}
}
The thing with this is, when I run the app on the simulator, I am restricted from pressing buttons and then sometimes I can press them and then sometimes they get restricted again. This bug is so confusing because it makes no sense where it springs from.
The other issue is I can use a Constants value in all the query functions in my collections path.
static let schoolCollectionName = "school_users/\(user?.uid)/events"
This is nested in a Firebase struct within the Constants struct. In order to keep Xcode from giving errors I create a let users = Auth.auth().currentUser variable outside the Constants struct. The issue with this value is that when I put that in all of my query functions collection paths, all the buttons are accessible and selectable all the time, but when a user logs out and I log in as a new user, the previous user's data shows up in the new user's tableview.
It would obviously make more sense to use the Constants value because you prevent typos in the future, but I can't figure out how to get rid of the bug where the old user's data shows up in the new user's tableview. Thanks in advance.
The user id should definitely not be a constant. What it sounds like is that right now, you have no reliable way to change users -- your setup probably depends on which user is logged in at app startup, since that's where your variable gets set.
I would do something more like this:
func getEventName() {
guard let user = Auth.auth().currentUser else {
//handle the fact that you don't have a user here -- don't go on to the next query
return
}
listener = db.collection("school_users/\(user.uid)/events").order(by: "time_created", descending: true).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
Note that now, user.uid in the interpolated path doesn't have the ? for optionally unwrapping it (which Xcode is giving you a warning for right now). It will also guarantee that the correct query is always made with the currently-logged-in user.
Regarding being able to press the buttons, that sounds like an unrelated issue. You could run your app in Instruments and check the Time Profiler to see if you have long-running tasks that are gumming up the main/UI thread.

How to avoid crashes in tableView(_:cellForRowAt)?

In the Fabric, I see a lot of crashes in the "tableView(_:cellForRowAt)" section. There is not a certain scenario for this exceptions. Anytime and in the any screen it can occur.There is no data for analysing the crashes. Only I know there are crashes in "tableView(_:cellForRowAt)".
I want to prevent this kind of exceptions although I do not know the root cause. Can I use a method like preventing NullPointer Exception (if (!null)) ?
Below two crashes in the different code sections ;
let XXX = Constants.sharedInstance.url+"/service/photo/"+userdas[(indexPath as NSIndexPath).row].id!+"/"+userdas[(indexPath as NSIndexPath).row].photo!+"/2"
and
self.notificationModel[indexPath.row].userNot.XXX?.XXXImageView = image
From your code, it's clear that you're making a couple of explicit force unwraps that could lead you to crash.
userdas[(indexPath as NSIndexPath).row].id!
userdas[(indexPath as NSIndexPath).row].photo!
self.notificationModel[indexPath.row].userNot.XXX?.XXXImageView
I guess that in the third case XXXImageView is implicitly unwrapped UIImageView that also might be nil.
To avoid the crash in your first section you can use a guard
guard let id = userdas[indexPath.row].id,
let photo = userdas[indexPath.row].photo else {
return
}
let XXX = Constants.sharedInstance.url+"/service/photo/"+id+"/"+photo+"/2"
I'm not sure what you're doing in the second section, but you just need to check that you unwrapped parameters aren't nil as well

XCTests and PHPhotoLibrary

I have functionality in the app that saved picture in photo gallery. An I wonder how to test this code:
func saveInPhotoGallery() {
guard self.cameraOutput != nil else { return }
if self.cameraOutput is UIImage {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: (self.cameraOutput as? UIImage)!)
}, completionHandler: { (saved, error) in
guard error == nil else {
self.unsucessfullSavingOperation(error)
return
}
})
}
}
Let's assume now that I want to test in my case scenario that self.cameraOutput is and UIImage and sth went wrong and there is an error in completionHandler so I ended up in self.unsucessfullSavingOperation(error) method. This has separate tests of course, but what I want to cover is:
Make sure whenever something will went wrong with inserting image in Camera Roll I will end up calling this method
And when I try to call saveInPhotoGallery() in test target it produce Alert that this require access to your photo library (doh!). But there is a way to skip this alert in Unit Tests or check whanever it popup and press allow? (like I said, for this test, let's assume that I have this permissions)
Or there is a way to mock this behaviour?
Yes, I'd mock PHPhotoLibrary. The main thing you'll need to replace is your use of PHPhotoLibrary.shared() which creates a dependency to a concrete instance. Instead, we can depend on an abstraction, that is, a protocol.
Then production code can supply PHPhotoLibrary.shared() as the instance to use. Test code can supply a mock object.
Let me know if you need more elaboration on breaking the dependency, or on making a mock object, or both.

How to implement Login module as part of my iOS framework

I am building an iOS framework and it should provide some common module like Register, Forgot Password, Login and Profile etc. So, any application that imports my framework should able to use these screens as it is. The challenge that I am facing is navigating from one screen to another screen in my iOS framework code. When navigating from once screen Login(screen1) to another screen Forgot Password(screen2), the handler(callback) methods are being invoked in screen1 view controller instead of screen2 view controller. We tried using xib and storyboard, however I did not find a solution for this.
Can somebody please point out any example code which does the similar stuff ?
Am I missing some thing over here in understanding iOS concepts, I am building an iOS framework which includes some UI flows, Is it possible?
I would suggest a delegate pattern, because callbacks are more one-shot, while delegates serve better the purpose to continually assist the lifetime of an object. Anyway I've created an example to cope with your requirements, git it here (Framework + test app included)
It involves a LoginController, which is the main entry point and orchestra for the framework.
When you initialise it, you pass a callback which will be used to send events, including "forgot password" and "user wants to exit", those are defined in an enum.
public enum LoginFrameworkStatus {
case Login
case ForgotPassword
case Help
case Disaster
case Exited
case UserWantsExit
}
Class offer an entry point to start the process:
public func enterLoginWorkflow(on controller: UIViewController, callback: LoginFrameworkCallback) {
let myBundle = Bundle(for: LoginController.self)
if let navi = UIStoryboard(name: "LoginWorkflow", bundle: myBundle).instantiateInitialViewController() as? MySpecialNavigationController {
presentingController = controller
navi.loginController = self
self.callback = callback
controller.present(navi, animated: true, completion: {
//presented!
callback?( .Login, navi, self) //If reference to LoginController is lost, tell the callback there's a problem.. shouldn't happend because we have a strong reference on the navigation controller.
})
}
}
.. and an exit point:
public func leaveLoginWorkflow() {
presentingController?.dismiss(animated: true, completion: {
self.callback?(.Exited, nil,self)
})
}
So the main interface for your framework would be:
LoginController().enterLoginWorkflow(on: self) { (status, controller, loginController) in
print("\(status) in \(controller?.description ?? "No Controller")")
switch status {
case .UserWantsExit:
loginController?.leaveLoginWorkflow()
case .ForgotPassword:
loginController?.leaveLoginWorkflow()
default:
()
}
}
In the test app I included a minimum workflow for you to test.
Let me know if this is what you needed, or if you want to investigate the delegation pattern, which I think would be way more suitable for this.
Try 1) IcaliaLabs/LoginKit (https://github.com/IcaliaLabs/LoginKit). LoginKit is a quick and easy way to add a Login/Signup UX to your iOS app.
2) TigerWolf/LoginKit https://github.com/TigerWolf/LoginKit
We can create a framework just like Parse.com does. It's well known and great app that used by thousands of developers.
Refer https://github.com/parse-community/ParseUI-iOS

Is it possible to access system classes in swift?

I was trying to access the player inside WKWebView but I did some coding and it turned out it doesn't use AVPlayerViewController it uses a system class called WebFullScreenVideoRootVideoController
I used the code like this
the function in the picture is fired after a UIWindow appears
After that I started digging more and search for notifications fired by WebFullScreenVideoRootVideoController and some class called AVSystemController or something like that... it turned out it has multiple notifications two of them logically do what I want:
NowPlayingAppIsPlayingDidChange // first one
SomeClientPlayingDidChange // second one
But also the object that they return is called FigBaseObject
Is there anyway to access these objects "some hacky way :P" ?
This link should help you to find when to work on particular notification.
http://paulofierro.com/blog/2015/10/12/listening-for-video-playback-within-a-wkwebview
inside the notification, you can find the url
Extract video URL from NSNotification Asset
if let asset = notification.object?.asset as? AVURLAsset {
let videoURL = asset.URL
}
Did test for XAMAarin IOS, notification.object?.asset is not available, but not sure about swift.
Thanks

Resources