how to block ios swift when no network, any best practice? - ios

I want to block my app's ui when there is no network connectivity.
How can I do this?
Creating a blocking wide-as-screen transparent view
move it to the front when needed to block ui touches?
move it to the rear when network is back?
Is there a best UX practice for this backed up in swift implementation?

Instead of doing all that you can just disable user interaction for that particular view. Like below
[self.view setUserInteractionEnabled:NO];
self.view.userInteractionEnabled = false //swift implementation
This will disable user interaction for all subviews of that view

If code that handles internet connection is not in currently shown view controller
UIApplication.sharedApplication().keyWindow.rootViewController.view.userInteractionEnabled = false

It's not ideal but I have solution based on Nanayakkara project. AppDelegate creates MyConnectionManager which is observed on networkStatusChanged selector:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("networkStatusChanged:"), name: ReachabilityStatusChangedNotification, object: nil)
Reach().monitorReachabilityChanges()
Each time connection state was changed manager calls networkStatusChanged and checks if connection is lost & top view isn't special connection view with message like "Please check your internet connection". If it isn't manager retrieves topController from sharedApplication
func topController() -> UIViewController? {
if var topRootController =
UIApplication.sharedApplication().keyWindow?.rootViewController {
while((topRootController.presentedViewController) != nil) {
topRootController = topRootController.presentedViewController!
}
return topRootController
}
return nil
}
and calls presentViewController with ConnectionViewController.

Related

How to detect if iOS devices is connected using wifi in Swift?

In an old, Objective-C based project I have been using the below code to detect if the iOS devices is currently connected using Wifi (not cellular).
My attempts to translate this code into Swift 5 failed due to the Objective-C pointers. Is there a clean way to use this solution in Swift?
Or are are there better ways to solve this nowerdays? I found solutions using the Reachability port to Swift or NWPathMonitor(). While they seem to work in general, these solution are used to monitor the connection state and send notifications on changes while one time checks are not (well) supported.
Event though these solution could be used to get the current connection state, this is done using delegate callback methods or closures. Thus it is not possible to use these solutions in existing code which was created to work "synchronously" (without callbacks/closures).
Is there a simply way to use localWiFiAvailable in Swift?
The code:
+ (BOOL)localWiFiAvailable {
struct ifaddrs *addresses;
struct ifaddrs *cursor;
BOOL wiFiAvailable = NO;
if (getifaddrs(&addresses) != 0) return NO;
cursor = addresses;
while (cursor != NULL) {
if ((cursor -> ifa_addr -> sa_family == AF_INET) && !(cursor -> ifa_flags & IFF_LOOPBACK)) { // Ignore the loopback address
// Check for WiFi adapter
#if TARGET_IPHONE_SIMULATOR
wiFiAvailable = true;
break;
#else
if (strcmp(cursor -> ifa_name, "en0") == 0) {
wiFiAvailable = YES;
break;
}
#endif
}
cursor = cursor -> ifa_next;
}
freeifaddrs(addresses);
return wiFiAvailable;
}
Details on why NWPathMonitor() cannot be used:
As #baronfac pointed out in his comment NWPathMonitor() can also deliver the current state, but this can only be done using its .pathUpdateHandler closure.
I am using a third-party library where I can override a souldSendData() -> Bool method. Sending the data should not be allowed on mobile connection but only on WiFi. The methodes requires an instant decision to return true or false. Waiting for the closure is thus not possible.
So, I am limited by the existing class here. Yes, connection could change any second, however this is a different problem. e.g. NWPathMonitor can be used to cancel the transfer when connection changes to mobile.
Solving this problem in Objectiv-C was no problem using the code shown above. The question is simply, if such a "direct" solution is possible in Swift as well. While using the Objectiv-C code in the Swift project would be possible I would prefer to keep the project Swift only.
As mentioned by Paulw11, the recommended approach is using NWPathMonitor. A common practice is the following within a UIViewController - class:
private var monitor: NWPathMonitor?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
monitor = NWPathMonitor()
monitor?.pathUpdateHandler = { [weak self] path in
if !path.isExpensive { // this means the device is connected via WiFi
// enter your code here
}
}
let queue = DispatchQueue(label: "Monitor")
monitor?.start(queue: queue) // start to monitor the connection
}
override func viewWillDisappear(_ animated: Bool) {
monitor?.cancel() // end to monitor the connection
super.viewWillDisappear(animated)
}
EDIT:
Thanks to FLichter and Rob Napier for the clarification. Maybe it helps to use this approach:
func shouldSendData() -> Bool {
let monitor = NWPathMonitor()
return !monitor.currentPath.isExpense
}

How to Prevent variables to be initialized twice?

I am new to iOs app development and I wonder if my app architecture is correct. A bug in my app make me believe that some of my variables are initialized several times.
To summarize my app : 2 screens and 2 views but only one controller. Depending the current view, the viewDidLoad have different results.
I don't believe this is the right way to do. I guess the idea would be to create a controller for each view ?
But my main concern here : in my viewDidLoad, when the main screen is loaded, I set up a notification observer. I believe( because of bugs ) that this observer is setup each time the screen load and then is called multiple times.
My questions here : Where to put this listener , is there a place that will run the code only once this view is loaded ? It should be fixed by putting this listener into a variable ?
Is the AppDelegate application function a right place for that kind of things ?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Remove iphone sleep mode
UIApplication.shared.isIdleTimerDisabled = true
//Setup the external scanner object
self.scanner.delegate = self
self.scanner.connect()
// Init the saved values
let defaults = UserDefaults.standard
// --------------- MAIN VIEW ---------------
if(mainView != nil){
// Add a notification receiver
// Will receive results ### request
NotificationCenter.default.addObserver(self, selector: #selector(mainTextNewLineNotification), name: Notification.Name(rawValue: "sendingToView"), object: nil)
// Layout setup
mainTextView.layer.cornerRadius = 6.0
[...]
}
// --------------- SETTINGS VIEW ---------------
if(settingsView != nil){
//Fill the field with saved values
inputHost.text = defaults.string(forKey: "hostname")
inputPort.text = String(defaults.integer(forKey: "port"))
if(defaults.string(forKey: "timeout") != nil){
inputTimeout.text = defaults.string(forKey: "timeout")
}
if(UserDefaults().string(forKey: "confirmSwitch") == "On"){
confirmSwitch.isOn = true
} else {
confirmSwitch.isOn = false
}
}
}
You don't need to care about removeObserver logic in your case (since you use simple subscription using -selector, not -block). From Apple doc:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc(deinit) method.
Each time when you initialize instance of UIViewController and if system loads its view viewDidLoad will be called. viewDidLoad is called once during UIViewController lifecycle. So your logic for subscription is correct.
I don't know your application entire logic so it is hard what is the reason of your bug.

Calling screen dismiss on accept the twilio voice call even app is active in swift 4

I’m using Twilio’s Programmable Voice in one of the projects. My primary requirement is to place VoIP class between mobile devices. I am able to place calls from one device to another,but when i accept the call at that time calling screen is dismiss automatically and call continue in background. In this case user do not have an option for disconnect call or any other action related to call because screen is dismissed.
Here is the screen that i have created for call when app is in foreground.
Calling placed success fully but on receiver accept it will dismiss the custom screen.So that user do not have any option to disconnect call or any other action related to call.
If any issue in code or any thing related to call kit setting i need to configure or any other issue ? Please help.
As per my knowledge this is default behaviour of call kit framework. On accept button click it will dismiss the screen when app is in foreground. If you wants to achieve same like whats app then you need to create a custom screen for that.Below code I did to resolve this issue:
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction)
{
NSLog("provider:performAnswerCallAction:")
// TwilioVoice.configureAudioSession()
let vc = loadVC(strStoryboardId: SB_CHAT, strVCId: idVoiceCallVC) as! VoiceCallVC
vc.callername = name
vc.userPhoto = userphoto
APP_DELEGATE.appNavigation?.pushViewController(vc, animated: true)
assert(action.callUUID == self.callInvite?.uuid)
TwilioVoice.isAudioEnabled = false
self.performAnswerVoiceCall(uuid: action.callUUID)
{ (success) in
if (success)
{
action.fulfill()
}
else
{
action.fail()
}
}
action.fulfill()
}
You just need to add your custom screen display code in this delegate method of call kit framework.
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {}
Thanks.

IOS Reachibility in whole app and just on on view load

I've added the following code to the viewDidLoad of my view controller:
let reachability: Reachability
do {
reachability = try Reachability.reachabilityForInternetConnection()
} catch {
print("Unable to create Reachability")
return
}
reachability.whenReachable = { reachability in
// this is called on a background thread, but UI updates must
// be on the main thread, like this:
dispatch_async(dispatch_get_main_queue()) {
if reachability.isReachableViaWiFi() {
print("Reachable via WiFi")
} else {
print("Reachable via Cellular")
}
}
}
reachability.whenUnreachable = { reachability in
// this is called on a background thread, but UI updates must
// be on the main thread, like this:
dispatch_async(dispatch_get_main_queue()) {
print("Not reachable")
}
}
do {
try reachability.startNotifier()
} catch {
print("Unable to start notifier")
}
It is notifying me of when the view loads however not when the state changes after the load. I'm wondering how to expand this so that I can have a notification anywhere in the application an not just on when the view loads as the internet may come back after a view loads.
Thanks
You should declare the reachability variable as instance variable and not only as local variable within a function. You could further use notifications to make the information available across your app. Just make sure the instance containing the reachability variable lives during the lifecycle of your app.
start reachability in AppDelegate and make it notify all registered observers creating and posting a notification, so you can register every controller you need, as "BostonMacOSX" suggested.
I recommend you create something along the lines of a connectivity controller that manages all reachability related events & holds the instance itself. Then this controller can fire events to all listening view controllers about changes in internet connectivity. Initialize and start the reachability notifier in your app delegate after didFinishLaunchingWithOptions

My iOS app freezes but no error appears

Does any body know what I need to check if app freezes after some time? I mean, I can see the app in the iPhone screen but no view responds.
I did some google and i found that, i've blocked the main thread somehow.
But my question is how to identify which method causes blocking of main thread? is there any way to identify?
Launch your app and wait for it to freeze. Then press the "pause" button in Xcode. The left pane should show you what method is currently running.
Generally, it is highly recommended to perform on the main thread all animations method and interface manipulation, and to put in background tasks like download data from your server, etc...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//here everything you want to perform in background
dispatch_async(dispatch_get_main_queue(), ^{
//call back to main queue to update user interface
});
});
Source : http://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks
Set a break point from where the freeze occurs and find which line cause that.
Chances may be,Loading of large data,disable the controls,overload in main thread,Just find out where that occurs using breakpoints and rectify based on that.
I believe it should be possible to periodically check to see if the main thread is blocked or frozen. You could create an object to do this like so:
final class FreezeObserver {
private let frequencySeconds: Double = 10
private let acceptableFreezeLength: Double = 0.5
func start() {
DispatchQueue.global(qos: .background).async {
let timer = Timer(timeInterval: self.frequencySeconds, repeats: true) { _ in
var isFrozen = true
DispatchQueue.main.async {
isFrozen = false
}
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + self.acceptableFreezeLength) {
guard isFrozen else { return }
print("your app is frozen, so crash or whatever")
}
}
let runLoop = RunLoop.current
runLoop.add(timer, forMode: .default)
runLoop.run()
}
}
}
Update October 2021:
Sentry now offers freeze observation, if you don't wanna roll this yourself.
I reached an error similar to this, but it was for different reasons. I had a button that performed a segue to another ViewController that contained a TableView, but it looked like the application froze whenever the segue was performed.
My issue was that I was infinitely calling reloadData() due to a couple of didSet observers in one of my variables. Once I relocated this call elsewhere, the issue was fixed.
Most Of the Time this happened to me when a design change is being called for INFINITE time. Which function can do that? well it is this one:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
Solution is to add condition where the function inside of viewDidLayoutSubviews get calls only 1 time.
It could be that another view is not properly dismissed and it's blocking user interaction! Check the UI Debugger, and look at the top layer, to see if there is any strange thing there.

Resources