Subview / Loading indicator not visible - ios

Mission: Generic Loader
Created a generic LoadingViewController to handle loading and display a loading indicator from any UIView (see photo).
Issue: Not always displayed
The indicator is not displayed if the user clicks the refresh button (top left) which immediately downloads relevant data and should display the loading indicator in the meantime.
However if the user clicks the filter icon (top right) and presented another UIView (push) and selects a few options and is delegated back to the mapView the loader indicator displays as expected - during loading data and is then removed.
The frame for the subView holding the loadingIndicator seems fine (0,0, 375, 667) in both instances. What could be at play?
Refresh Button
#IBAction func refreshPlaces(sender: AnyObject) {
downloadStores = true
updateStores()
}
Delegate Function from Filter
func filterViewDidFinish(controller: BudgetTableViewController, download: Bool) {
// Dismiss modal
self.navigationController!.popToRootViewControllerAnimated(true) // Pop back to map
// Update stores
downloadStores = download
updateStores()
}
Update Stores
func updateStores(){
// Display loading indicator
println("Display loading indicator")
let subViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoadingViewController") as! LoadingViewController
let loadingSubView: UIView = subViewController.view
loadingSubView.tag = 1 // Unique identifier
// Add loading indicator
self.navigationController!.view.addSubview(loadingSubView)
// Verify frame is OK
println("Frame: \(self.navigationController!.view.viewWithTag(1)?.frame)")
// Async
dispatch_async(dispatch_get_main_queue(), {
// Update location
subViewController.status.text = "Updating your location"
self.getCoordinates()
// Get stores
if self.downloadStores == true {
subViewController.status.text = "Finding stores nearby"
self.getStores()
}
// Create markers
self.googleMapsMarkers()
// Dismiss loading indicator
println("Remove loading indicator")
self.navigationController!.view.viewWithTag(1)?.removeFromSuperview()
})
}
UPDATE
The Loading Indicator gets displayed also on the 'refresh button' if I remove the self.navigationController!.view.viewWithTag(1)?.removeFromSuperview().
Question is WHY is it being removed before the data is downloaded (i.e. the functions higher in the Async que are finished)?
I understood the async que as that things operate in the background until they are done, and that a function entered into the que should only be executed when a previous function in the que is done - but perhaps I am mistaken?
New to swift... :)

You should not be adding views directly into your navigation controller's view. If you want to present a child view controller that overlays over your current view you should do it correctly by calling the right api methods:
subViewController.view.frame = self.view.bounds;
[self.view addSubview:subViewController.view];
[self addChildViewController:subViewController];
[subViewController didMoveToParentViewController:self];

Maybe this might be helpful for you. Have a look at UIView extension where activity indicator is added to view

Related

Where is the back button graphic Apple uses in the navigation stack?

I want to add code to the back button presented in UINavigationController. But, I want the back button to look identical to what Apple presents.
I have this all setup to use my graphic in the leftBarButton, but am unable to get the graphic to look perfect. To that end, is there a way that I can use the internal iOS back button in my own custom button?
Unfortunately the navigation bar's back button is not very customizable. There is no simple way to access this image.
If you're familiar with Sketch or Photoshop, I suggest you take a screenshot of the back button and trace your image over the exact location.
If you can't do this, you technically can access the back buttons image through some minor UI manipulation. You'll have to first have a back button which is on screen. Once you know it's on screen, such as in your viewDidAppear, you'll want to look through the subviews of your leftBarButtonItem. One way you can do this is by calling navigationBar.subviews and navigating until the view. Another way is to expose the items target view.
extension UIBarButtonItem {
var targetView: UIView? {
guard let view = value(forKey: "view") as? UIView else {
return nil
}
return view
}
}
Now you can call leftBarButtonItem.targetView.subviews. Your for-loop would look something like,
for subview in leftBarButtonItem.targetView.subviews {
if let imageView = subview as? UIImageView {
self.image = imageView?.image
}
}
All of this is pseudo code and untested. Typically UIKit will use the standard classes (such as UIImageView) when building their views. However in older classes, they have been known to draw images manually. So if there is no image, you can always resort to taking a snapshot of the view with the arrow.
view.snapshotView(afterScreenUpdates: false)
Once you have your image / view, you'll save it in a property (most likely in your custom navigation controller) and then you'll have access to it whenever you push new view controllers.

Subview keeps moving when I push and pop navigation controller

image before pushing
This is what it's supposed to look like originally. The slider's added as a subview of the main view from Xib file on viewWillLoad, and I don't add it again if the subview exists on viewDidLoad. When I push, I call hidesBottomBarWhenPushed on the other view controller.
This is what I happens when I pop back:
image after popping
I have no idea why the subview does that.
Whatever you have set for bottom slider in viewDidLoad is the first time setting when view comes appear on screen. After that you have hide slider on push action, and goes to second view….right?
But when you come back to your view with pop, then how view can identify that it have show or hide that slider…? So, when you come back-I mean pop that time viewWillAppear is called. Put you code there…
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// whatever you want to do, this is just for suggestion
if (bottomSlider.hidden == TRUE) {
bottomSlider.hidden = FALSE;
}
else {
bottomSlider.hidden = TRUE;
}
}

Entire View stuck when UIButton is pressed

I have a good amount of calculation codes get executed when the UIButton is pressed. But when the button is pressed, the whole view just get stuck and the button stay highlighted until the calculation finished.
For example, I set a loading view .hidden = false when the button is pressed too, but the loading view just doesn't show up during the calculation.
Is there any way to fix this?
Thanks in advance!
You should have to do the things in background from the updates you want to run on the UI:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// do your task
dispatch_async(dispatch_get_main_queue()) {
// update some UI
}
}

presenting a modal view controller while the keyboard is active

So I basically have a form, consisting of several text fields. The user types into the fields as usual. But the user also has the option of double-tapping a text field, which presents a modal view controller, allowing the user to choose from a number of options relating to that field.
Can I somehow present the modal "over" the keyboard, such that when it is dismissed, the keyboard is still active for the field that had been first responder before I presented the modal?
Right now, the keyboard dismisses while the modal appears, and reappears as the modal is dismissed. It looks clunky to me, and distracting. Would love to streamline it, and reduce the amount of animation onscreen.
Edit: I've updated this answer for iOS 12 and Swift. The revised example project (containing new Swift and updated Objective-C implementations) is here.
You can create a new UIWindow and place that over the default window while hiding the keyboard's window.
I have an example project on Github here, but the basic process is below.
Create a new UIViewController class for your modal view. I called mine OverlayViewController. Set up the corresponding view as you wish. Per your question you need to pass back some options, so I made a delegate protocol OverlayViewController and will make the primary window's root view controller (class ViewController) our delegate.
protocol OverlayViewControllerDelegate: class {
func optionChosen(option: YourOptionsEnum)
}
Add some supporting properties to our original view controller.
class ViewController: UIViewController {
/// The text field that responds to a double-tap.
#IBOutlet private weak var firstField: UITextField!
/// A simple label that shows we received a message back from the overlay.
#IBOutlet private weak var label: UILabel!
/// The window that will appear over our existing one.
private var overlayWindow: UIWindow?
Add a UITapGestureRecognizer to your UITextField.
override func viewDidLoad() {
super.viewDidLoad()
// Set up gesture recognizer
let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
doubleTapRecognizer.numberOfTapsRequired = 2
doubleTapRecognizer.delegate = self
firstField.addGestureRecognizer(doubleTapRecognizer)
firstField.becomeFirstResponder()
}
UITextField has a built-in gesture recognizer, so we need to allow multiple UIGestureRecognizers to operate simultaneously.
extension ViewController: UIGestureRecognizerDelegate {
// Our gesture recognizer clashes with UITextField's.
// Need to allow both to work simultaneously.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
This is the interesting part. When the gesture recognizer is triggered, create the new UIWindow, assign your OverlayViewController as the root view controller, and show it. Note that we set the window level to UIWindowLevelAlert so it will appear in front. However, the keyboard will still be in front despite the alert window level, so we have to manually hide its window, too.
It is important to not set the new UIWindow as key or to change the first responder from the UITextField or the keyboard will be dismissed.
Previously (before iOS 10?) we could get away with overlayWindow.makeKeyAndVisible(), but now setting it as key will dismiss the keyboard. Also, the keyboard's window now has a non-standard UIWindow.Level value that is in front of every publicly defined value. I've worked around that by finding the keyboard's window in the hierarchy and hiding it instead.
#objc func handleDoubleTap() {
// Prepare the overlay window
guard let overlayFrame = view?.window?.frame else { return }
overlayWindow = UIWindow(frame: overlayFrame)
overlayWindow?.windowLevel = .alert
let overlayVC = OverlayViewController.init(nibName: "OverlayViewController", bundle: nil)
overlayWindow?.rootViewController = overlayVC
overlayVC.delegate = self
// The keyboard's window always appears to be the last in the hierarchy.
let keyboardWindow = UIApplication.shared.windows.last
keyboardWindow?.isHidden = true
}
The overlay window is now the original window. The user can now select whatever options you built into the overlay view. After your user selects an option, your delegate should take whatever action you intend and then dismiss the overlay window and show the keyboard again.
func optionChosen(option: YourOptionsEnum) {
// Your code goes here. Take action based on the option chosen.
// ...
// Dismiss the overlay and show the keyboard
overlayWindow = nil;
UIApplication.shared.windows.last?.isHidden = false
}
The overlay window should disappear, and your original window should appear with the keyboard in the same position as before.
I can't try this right now, but have implemented similar for other purposes. In the action for presenting the modal controller, I assume gesture recognizer or delegate method, first take a screenshot and place it in an imageView over the current subviews. Later when returning, simply remove the imageView.
Might sound crazy but I remember having done this for a transition where the keyboard moving during the transition caused similar clunky behavior. It was not difficult to implement at all.
If you have trouble trying it, perhaps someone will provide some code. I can reference my own work later and add an example, but not now.
#Rob Bajorek's answer is excellent.
For iOS 9,10 there are small changes.
Instead of the code:
[self.overlayWindow setWindowLevel:UIWindowLevelAlert];
[self.overlayWindow makeKeyAndVisible];
Put the following code:
NSArray *windows = [[UIApplication sharedApplication] windows];
UIWindow *lastWindow = (UIWindow *)[windows lastObject];
[self.overlayWindow setWindowLevel:lastWindow.windowLevel + 1];
[self.overlayWindow setHidden:NO];
In order to keyboard to visible any of text accepting fields such UITextField or UITextView or UISearchBar should be the first responder and they should be visible in the view. Meaning responding view should be in the top level hierarchy in the window.
If you don't need this effect, Instead of presenting a ViewController you can add ViewController.view as a subview of your self.view with animation.
You have access to the frame of the keyboard in iOS.
You need to implement code to listen to the keyboard notifications (like UIKeyboardWillShowNotification and UIKeyboardWillChangeFrameNotification). The notification will send you informations about the frame of the keyboard.
Giva a look to the description of the "Keyboard Notification User Info Keys" in the windows reference.
You'll find useful for you purpose:
UIKeyboardBoundsUserInfoKey The key for an NSValue object containing a CGRect that identifies the bounds rectangle of the
keyboard in window coordinates. This value is sufficient for obtaining
the size of the keyboard. If you want to get the origin of the
keyboard on the screen (before or after animation) use the values
obtained from the user info dictionary through the
UIKeyboardCenterBeginUserInfoKey or UIKeyboardCenterEndUserInfoKey
constants.
With the information of the keyboard frame you can show there you modal view.
Just add an tap gesture in your textfield and a UITextfield *flagTextfield;
UITapGestureRecognizer* doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(DoubleTapMethod:)];
doubleTap.numberOfTapsRequired = 2;
[self.txtTest addGestureRecognizer:doubleTap];
-(void)DoubleTapMethod:(UITapGestureRecognizer *)gesture
{
[flagTextfield resignFirstResponder];
NSLog(#"DoubleTap detected");
//Set your logic on double tap of Textfield...
//presents a modal view controller
}
- (void)textFieldDidBeginEditing:(UITextField *)textField{
flagTextfield = textfield;
}

Present a UIScrollView when opening an App for the first time

I have an iPad App that I want to be compatible from iOS 5.0 to 6.0. My main view contains a scroll view z-indexed on the front, which is initially set to hidden. I also have a toolbar containing a button that cycle the scroll view hidden or not.
I would like to add a feature to present the scroll view as initially visible when the user opens the App for the first time to make the help visible by default to new users.
My code to cycle between visible and hidden is the following:
- (void)showHelpView:(id)sender {
BOOL hidden = [blackTranslucent isHidden];
[self.view bringSubviewToFront:scrollViewOutlet];
if (hidden) {
[scrollViewOutlet setHidden:FALSE animationStyle:KGAnimationFade duration:0.7];
[blackTranslucent setHidden:FALSE animationStyle:KGAnimationFade duration:0.5];
}
else {
[scrollViewOutlet setHidden:TRUE animationStyle:KGAnimationFade duration:0.5];
[blackTranslucent setHidden:TRUE animationStyle:KGAnimationFade duration:0.7];
}
}
where the sender is my toolbar button, blackTranslucent is a view on top of the main view and scrollViewOutlet is my scroll view IBOutlet.
Add a property "isNewUser" on NSUserDefaults in the application:willFinishLaunchingWithOptions: method which will only be written once by checking if the key exists.
In your main view in the viewDidLoad check this property if true make the view visible and update the key to false. if not just continue regularly.
Further information on NSUserDefaults
Hope that helps

Resources