UIViewControllerTransitioningDelegate functions not called for container view controller - uiviewcontroller

I'm adding a View Controller's view to a container view programmatically and want it to have an animated transition but the UIViewControllerTransitioningDelegate functions aren't called, even if I call beginAppearanceTransition(animated:).
let secondViewController = SecondViewController()
self.secondViewController = secondViewController
secondViewController.view.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(secondViewController.view)
containerView.addConstraints(secondViewController.view.pinnedConstraints(view: containerView))
secondViewController.transitioningDelegate = self
secondViewController.modalPresentationStyle = .overCurrentContext
secondViewController.didMove(toParent: self)
secondViewController.beginAppearanceTransition(true, animated: true)
When executing the code above, animationController(forPresented:presenting:source:) -> UIViewControllerAnimatedTransitioning? is never called in the current view controller

Related

test case not detecting update in ViewContainerRef

My html uses an ng-template. The template is to create thumbnails.
<ng-template #thumbnailTemplate let-option="option">
<div id="{{option.divId}}"> <!-- top level div of thumbnail. This will have ids thumbnail-1, thumbnail-2 etc.-->
<img id="{{option.imgId}}" src="{{option.imgSrc}}"> <!-- this will have width, height=80-->
<!-- the X button is created using CSS. This will have ids close-button-1, close-button-2. They'll also containn reference to the parent div id (thumbnail-1, thumbnail-2 ) -->
</div>
</ng-template>
The thumbnails gets created when a file is selected from an input element. FileReader sends load event and my event handler is called which should create a thumbnail by adding a view in the container
handleReaderLoaded(event:FileReaderProgressEvent) {
console.log("got load event of file reader ",event);
let thumbnailTemplateViewRef:EmbeddedViewRef<any>;
let imageString = event.target.result;//this will be like data:image/png;base64,ZGQ=ZGF0YTppbWFnZS9wbmc7YmFzZTY0LFpHUT0=
console.log("result from file load: ",imageString);
console.log("consecutive generator is ",this.consecutiveIdGenerator);
//create new ids for div, img and a in the template
++this.consecutiveIdGenerator;
let divId = "thumbnail-"+(this.consecutiveIdGenerator);
console.log("div id "+divId);
let imgId = "img-"+(this.consecutiveIdGenerator);
console.log("img id "+imgId);
let closeId = "close-button-"+(this.consecutiveIdGenerator);
console.log("close Id is "+closeId);
console.log("thumbnail container length was "+this.thumbnailContainerRef.length);
//TODOM - define context as a class so that it can be used in new question and question details
thumbnailTemplateViewRef = this.thumbnailContainerRef.createEmbeddedView(this.thumbnailTemplateRef,{option:{divId:divId,
imgId:imgId,
closeId:closeId,
imgSrc:imageString}});
//store the reference of the view in context of the template. This will be used later to retrive the index of the view when deleting the thumbnail
thumbnailTemplateViewRef.context.option.viewRefId = thumbnailTemplateViewRef;
console.log("thumbnail container length is "+this.thumbnailContainerRef.length);
}
Now I want to test handleReaderLoaded and check that it updates the thumbnailContainerRef by adding thumbnailTemplateViewRef in it.
The spec I have written is
fit('should upload image if user selects an image', () => {
let newPracticeQuestionComponent = component;
expect(newPracticeQuestionComponent.currentImageAttachmentCount).toBe(0);
expect(newPracticeQuestionComponent.thumbnailContainerRef.length).toBe(0);
let file1 = new File(["foo1"], "foo1.txt");
let reader = newPracticeQuestionComponent.handleFileSelect([file1]);//the callback for FileReader load method is assigned in this function. The callback is handleReaderLoaded
fixture.detectChanges();
expect(newPracticeQuestionComponent.currentImageAttachmentCount).toBe(1);
expect(newPracticeQuestionComponent.thumbnailContainerRef.length).toBe(1);
done(); //wait
console.log('done here');
});
My test case is failing because expect(newPracticeQuestionComponent.thumbnailContainerRef.length).toBe(1); is coming out as 0.
What am I doing wrong?
seems, I didn't understand the purpose of done correctly. I thought if I'll use done, the script will automatically wait but it doesn't (as is clear from the following trace)
reading file --> this is in handleFileSelect
context.js:1972 done here -->ths is in handleFileSelect
context.js:1972 got load event of file reader ProgressEvent {isTrusted: true, lengthComputable: true, loaded: 4, total: 4, type: "load", …} --> this is in the callback handleReaderLoaded. So the spec finished before the callback was called.
I done acts as a checkpoint in Jasmine. When Jasmine sees that a spec uses done, it knows that it cannot proceed to the next step (say run next spec) unless the code leg containing done has been called.
I re-wrote the spec to and created the checkpoint using done as follows
it('should upload image if user selects an image', (done) => {
let newPracticeQuestionComponent = component;
expect(newPracticeQuestionComponent.currentImageAttachmentCount).toBe(0);
expect(newPracticeQuestionComponent.thumbnailContainerRef.length).toBe(0);
let imageThumbnailDiv = fixture.debugElement.query(By.css("#thumbnail-1"));
let imageThumbnailImg = fixture.debugElement.query(By.css('#img-1'));
let imageThumbnailClose = fixture.debugElement.query(By.css('#close-button-1'));
//there should not be any HTML code which contains thumbnail
expect(imageThumbnailDiv).toBeFalsy();
expect(imageThumbnailImg).toBeFalsy();
expect(imageThumbnailClose).toBeFalsy();
let file1 = new File(["foo1"], "foo1.txt");
let reader = newPracticeQuestionComponent.handleFileSelect([file1]);
//file upload is async. so give time for `load` event of FileReader to be triggered and handled
setTimeout(function() {
console.log("in timeout");
fixture.detectChanges();//without this, the view will not be updated with model
expect(newPracticeQuestionComponent.currentImageAttachmentCount).toBe(1);
expect(newPracticeQuestionComponent.thumbnailContainerRef.length).toBe(1);
//the html for thumbnail should be created now
let imageThumbnailDiv2 = fixture.debugElement.query(By.css("#thumbnail-1"));
let imageThumbnailImg2= fixture.debugElement.query(By.css('#img-1'));
let imageThumbnailClose2 = fixture.debugElement.query(By.css('#close-button-1'));
expect(imageThumbnailDiv2).toBeTruthy();
expect(imageThumbnailImg2).toBeTruthy();
expect(imageThumbnailClose2).toBeTruthy();
done();//without done, jasmine will finish this test spec without checking the assertions in the timeout
}, 2000);
//if done is not use, jasmine will just finish the current spec without checking any assertions
});

Swipe from UITableView to UIButton via UIFocusGuide not working

I’m having trouble swiping right to move focus from a UITableView to a UIButton that is to the right and below the tableview. I’ve set up a UIFocusGuide and I believe I’ve got the geometry correct. (See attached screenshot, created using Pod VisualFocusGuide.)
When I use the built-in QuickLook on the UIFocusUpdateContext, it shows only the table view focus (the highlighted row in one color, and the other rows in another color), but that might be because I can only ever get focus in the table view, so perhaps that context is limited to the table.
I’ve seen a recommendation elsewhere to associate the focus guide with the button instead of the controller’s view, but that didn’t work. Out of desperation, I also tried associating the focus guide with the tableview, but still no luck.
Here are the pertinent methods. Any light anyone can shed will be appreciated! Thanks!
override func viewDidLoad() {
super.viewDidLoad()
self.view.addLayoutGuide(focusGuide)
self.focusGuide.widthAnchor.constraint(equalTo: self.infoButton.widthAnchor).isActive = true
self.focusGuide.heightAnchor.constraint(equalTo: self.tableView.heightAnchor).isActive = true
self.focusGuide.topAnchor.constraint(equalTo: self.tableView.topAnchor).isActive = true
self.focusGuide.leftAnchor.constraint(equalTo: self.infoButton.leftAnchor).isActive = true
self.focusGuide.preferredFocusEnvironments = [self.infoButton]
}
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
super.didUpdateFocus(in: context, with: coordinator)
guard let nextFocusedView = context.nextFocusedView else { return }
// When the focus engine focuses on the focus guide, we can programmatically tell it which element should be focused next.
switch nextFocusedView {
case self.tableView:
self.focusGuide.preferredFocusEnvironments = [self.infoButton]
case self.infoButton:
self.focusGuide.preferredFocusEnvironments = [self.tableView]
default:
self.focusGuide.preferredFocusEnvironments = []
}
}
I've just gotten this to work. I added another focus guide below the table view, to the left of the button. I sized it to be the width of the tableview and the height of the button. I made its preferredFocusEnvironment the tableView. But that in itself wasn't enough. I then removed the didUpdateFocus method, and left and right swiping started working! Here's the resulting code:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addLayoutGuide(focusGuide1)
self.focusGuide1.topAnchor.constraint(equalTo: self.tableView.topAnchor).isActive = true
self.focusGuide1.leftAnchor.constraint(equalTo: self.infoButton.leftAnchor).isActive = true
self.focusGuide1.heightAnchor.constraint(equalTo: self.tableView.heightAnchor).isActive = true
self.focusGuide1.widthAnchor.constraint(equalTo: self.infoButton.widthAnchor).isActive = true
self.focusGuide1.preferredFocusEnvironments = [self.infoButton]
self.view.addLayoutGuide(focusGuide2)
self.focusGuide2.topAnchor.constraint(equalTo: self.infoButton.topAnchor).isActive = true
self.focusGuide2.leftAnchor.constraint(equalTo: self.tableView.leftAnchor).isActive = true
self.focusGuide2.heightAnchor.constraint(equalTo: self.infoButton.heightAnchor).isActive = true
self.focusGuide2.widthAnchor.constraint(equalTo: self.tableView.widthAnchor).isActive = true
self.focusGuide2.preferredFocusEnvironments = [self.tableView]
}

UINavigationController disappears when I switch to a different tab while UISearchController is visible

In my app I have a UITabBarController with several UINavigationController's. I am trying to add a search feature like in Apple's Music app, in which a search icon appears in every UIViewController's navigatonItem. When this icon is tapped, a UISearchController is presented.
The problem I am facing is that if I present the UISearchController and switch to a different tab (before typing anything in the UISearchController's searchBar), when I come back to that tab the UINavigationController is not visible anymore. Even if I cancel the search, the UISearchController is dismissed but I cannot see my original UINavigationController. If I do type something into the search bar before switching to a different bar and back, the search results remain always visible but when I dismiss the UISearchController my UINavigationController does not show up. I can only see the UITabBarController's tabBar and everything else completely white.
However, if once the UISearchController has been dismissed, if I go to another tab and then back to the original one, the UINavigationController appears again.
Here's the code for my UIViewController subclass:
let searchResultsController = SearchResultsController()
var searchController: UISearchController!
var searchButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
searchButton = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: Selector("showSearch:"))
navigationItem.rightBarButtonItem = searchButton
searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = self
searchController.searchBar.delegate = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = false
}
func showSearch(sender: AnyObject? = nil) {
presentViewController(searchController, animated: true, completion: nil)
}

Sending data from one uiviewcontroller to another navigationcontroller using present modally

I am new to swift i am using storyboard and have used navigationcontrollers to connect from one viewcontroller to another. I want to send the name of the image clicked to the next viewcontroller which is connected modally in storyboard from the imageView. I searched lot about transferring data from oneviewcontroller to another viewcontroller connected with navigationcontroller modally but no solution was available. Please let me know if any of the code is required as i dont know how to go ahead with it. I know this might be silliest question but posting this after searching a lot on net.
EDIT according to #uraimo reply.
Do i need to provide name to every segue i created on storyboard?.
I have 2 fixed images on viewcontrollerA and i have placed a uibutton with transparent background and no text on each of them and then ctrl drag to navigation controller of viewcontrollerB for presenting modally and unwinding the backbutton i.e. UIBarButtonItem to viewcontrollerA by ctrl drag the back button of viewcontrollerB to exit of the viewcontrollerB and unwinding it.
This is how i have created navigation from any of the image click out of 3 images of viewcontrollerA to viewcontrollerB and back to viewcontrollerA on back button click of viewcontrollerB.
Please let me know if i am doing anything wrong and will your prepareForSegue code be useful in accomplishing my task.
Basically, both using IB or when you do it programmatically, you have to configure your new viewcontroller with all the data it needs before the segue is performed (or the controller is presented via code).
In your case, just set the image name (your custom view controller class YourViewController should have a specific String property to hold this value) overriding prepareForSegue in the current view controller class:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "yourModalSegueIdentifier" {
let imgName= (sender as! UIImageView)
let destination = segue.destinationViewController as UINavigationController
let yourController = destination.topViewController as YourViewController
yourController.imageName= <name here>
}
}
This solves the passing data question.
But in your case, you need the name of the clicked image, and that can be only obtained adding a click event through a UIGestureRecognizer to the UIImageView.
So, you'll need a uigesturerecognized that on click will perform the segue you've created. And also, you will not be able to get the name of the image asset (the one you use the creating an UIImage using imageNamed:) because it's not available anymore, and yo'll have to use the accessibilityIdentifier.
This makes everything way more complicated, it seems it could be done for the most part graphically here and here(but it's not really clear how to do it), but it's usually done via code.
Simplifying it a bit using a global variable:
var currentImage = ""
func viewDidLoad(){
...
...
let aTap = UITapGestureRecognizer(target: self, action: Selector("imageTapped:"))
aTap.numberOfTapsRequired = 1
//For every image, configure it with recognizer and accessibilityId:
firstImage.userInteractionEnabled = true
firstImage.addGestureRecognizer(aTap)
firstImage.accessibilityIdentifier = "firsImage"
...
}
func imageTapped(recognizer:UITapGestureRecognizer) {
let imageView = recognizer.view as! UIImageView
currentImage = imageView.accessibilityIdentifier
self.performSegueWithIdentifier("yourModalSegueIdentifier", sender: self)
}
And change this:
yourController.imageName= <name here>
to this:
yourController.imageName= currentImage
Update:
Do i need to provide name to every segue i created on storyboard?
Yes, it's the only way to identify them, every UIStoryboardSegue has an identifier. But remember, segues are not the only way to go from a controller to another, if you do it completely programmatically (no segues) you usually call "presentViewController". Segues are a storyboard concept.
Again, regarding the segue name/identifier, you didn't need it until now because you never referenced that segue from your code, you need it for both prepareForSegue and performSegueWithIdentifier. Just select the segue and give it a name on the right inspector pane.
The structure you describe seems ok, the only thing it's that i'm not so sure that the UIButtons are really needed, try with a modal segue from the imageview or directly from the viewcontroller to the destination view controller.
Update 2:
If you are starting and need a free course that will teach you the basics and also make you build a few interesting ios apps i recommend hackingwithswift.
check out how I did this
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "") {
case "AddItem":
let destination = segue.destination as? UINavigationController
guard let itemViewController = destination?.topViewController as? ItemViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
itemViewController.collection = collection
case "EditCollection":
guard let collectionViewController = segue.destination as? EditCollectionViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
collectionViewController.collection = collection
default:
fatalError("Unexpected Segue Identifier; \(segue.identifier)")
}
}

How to set UIWebView initial position?

How to set the initial focus in a HTML page using UIWebView. For example, below is the HTML page. If i load this in UIWebView, it shows from (0,0) coordinates of a HTML view. But i want to change the starting position as described in the below image. I tried to change the offset, but no use.
webView.scrollView.contentOffset = CGPointMake(100, 100).
You probably shouldn't hard code this because for one reason, it's hard-coded. If google, or whatever website you want to display alters their layout your positions will be obsolete. And you will have to resubmit your entire app just to off-set this.
My recommendation is use auto layout and 'scales page to fit' for the UIWebView.
But your code is accurate. Make sure you put it in webViewDidFinishLoad: or viewDidLoad and your UIWebViewDelegate & UIScrollViewDelegate are added to your header file and called in your implementation file :
self.webView.delegate = self;
self.webView.scrollView.delegate = self;
Place the UIWebView into a subview. A generic UIView will work.
When instantiating the UIWebView, give it screen bounds with a slightly lower width than the containing UIView. Weird, I know.
Set the UIWebView's contentOffset in the webViewDidFinishLoad method of your UIWebViewDelegate
The following code works for me, in Xcode 6.1.1. To test, just make this ViewController class the root view controller for your app:
import UIKit
class ViewController: UIViewController, UIWebViewDelegate {
override func loadView () {
let screenBounds : CGRect = UIScreen.mainScreen().bounds
var slimmerBounds : CGRect = UIScreen.mainScreen().bounds
slimmerBounds.size.width = slimmerBounds.size.width - 1
let webView = UIWebView(frame: slimmerBounds)
let url = NSURL (string: "http://google.com/")
let req = NSURLRequest(URL: url!)
webView.loadRequest(req)
webView.delegate = self;
let contentView = UIView(frame: screenBounds)
contentView.addSubview(webView)
self.view = contentView
}
func webViewDidFinishLoad(webView: UIWebView) {
webView.scrollView.contentOffset = CGPointMake(0, 100)
webView.bounds = UIScreen.mainScreen().bounds
}
}
By the way ... this will set the initial position of all URLs loaded into this Web view. If you only want to position the first page, you'll need to add some additional logic to webViewDidFinishLoad