I have implemented a custom viewController transition whereby I present a new ViewController as a pop-over. This resembles something like an alertViewController. I have written the transition handler and set my presenting viewController as the delegate and than preset this presented view controller like this:
#IBAction func pickOption(sender: UIButton) {
let storyBoard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyBoard.instantiateViewControllerWithIdentifier("OptionVC")
vc.modalPresentationStyle = .OverCurrentContext
vc.transitioningDelegate = self
self.presentViewController(vc, animated: true, completion: nil)
}
Here is where I am confused about proceeding with the layout. I wish to have this presented ViewController take up a small fraction of the frame of the presenting ViewController's View, preferably using Autolayout to set margin constraints. However, both the ViewController's in this are separate scenes in Interface Builder.
In the transition handler, the canonical thing to do seems to be adding the presented ViewController's view to a "context" view and handle animations there. Then call transitionContext.completeTransition(). Now that the presented ViewController's view is a subview of the presenting ViewController's view, I would like the autolayout constraints to be present but as I previously stated this isn't obvious how to do given both views are in separate scenes in IB.
Thanks in advance for any advice for guidance.
Related
My iOS SwiftUI app has some WKWebViews in the layout.
It is possible by means of creating a special View that encloses the WKWebView as it is known from other SO questions.
Suitable methods are included:
func webView(_ webView: WKWebView,
didFinish navigation: WKNavigation!)
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
func makeUIView(context: Context) -> WKWebView
func updateUIView(_ uiView: WKWebView, context: Context)
The main screen has two WKWebViews, one on the left and the other on the right, in two different views that contain the WebViews (this is how I named the WKWebView enclosing View).
So there are two views in a HStack, and they split the screen.
Each of these views has its own layout but it also has a WebView inside.
The left WebView correctly displays an HTML page created by the app.
The right WebView has an editable text area inside.
The following was tested on the iOS simulator, I do not know if it is the same on real devices, but it is very likely.
When I edit some text, I think that the layout is calculated again and I experience that the two WebViews become blank,
and the updateUIView method is called.
The WebView in the body are referenced with a variable that is assigned outside of the body, so
it is like
LeftView
var body: some View
{
leftWebView
}
and not
var body: some View
{
LeftWebView()
}
so it should be not recreated.
Indeed I think it is not recreated, yet it becomes blank.
I found a possible solution or workaround.
I do not provide the code because I do not know if it is good, so I just summarize it up here.
It works for me.
Instead of embedding the WKWebView in the SwiftUI View
I embedded a ViewController in the SwiftUI View.
Just a few differences in the implementation on the SwiftUI View side.
Examples are easily found.
This ViewController hosts a view that is loaded from a xib file, it is a simple view, I created it in the designer tool, no outlets to create or other hassles.
Then the WKWebView is added to the UIView programmatically.
So, it happens that, this way the WKWebViews in my application are not bothered by the SwiftUI layout hierarchy cycles and are correctly displayed.
They do not become blank when one of them is edited or the layout changes.
I'm using a WKWebview on my story board. I need a way for clicking on specific text to perform an action.
Assume I have a webkit added:
#IBOutlet weak var webkit: WKWebView!
And then later on I have:
webkit.loadHTMLString("Click here or there for different actions", baseURL: nil)
How could I set this up so that clicking on the word "here" would perform code of my choosing, and clicking on "there" would perform different code of my choosing.
It needs to be set up in a webkit like this because I need to use html (the above is just an example).
I had thought about trying to do this through linking. For example:
webkit.loadHTMLString("Click <a href=''>here</a> or there for different actions", baseURL: nil)
And then run something like I found in this question's answer:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == WKNavigationType.linkActivated {
print("run code")
decisionHandler(WKNavigationActionPolicy.cancel)
return
}
decisionHandler(WKNavigationActionPolicy.allow)
}
However this seems messy as I don't actually need website links. And this only works for one specific action. Clicking on any link would allow me to run some kind of code, but I want specific code for specific words.
Any guidance would be greatly appreciated!
in watchOS I used presentControllerWithName to show a View Controller and to pass the context in this way
presentControllerWithName("NameOfTheViewController", context:"PassedContext")
Which is the equivalent in tvOS?
Best Regards
As noted in other answers, the way to programmatically show another view controller in tvOS (or iOS) is performSegueWithIdentifier:sender:. (Or presentViewController:animated:completion: if you're not getting your VCs from a storyboard flow.)
But you might not need to do it programmatically. In watchOS it's sometimes easiest to do it that way, but in iOS & tvOS, it's common to make controls directly perform storyboard transitions entirely from Interface Builder. Just control-drag (right-click-drag) from the button to another view controller. (More step-by-step instructions in Xcode Help.)
Unlike watchOS, the view controller transitions in iOS & tvOS don't include a way to pass context information. Not as part of the API, at least — you have to include a bit of glue code yourself to do that. How to do that is a pretty common question.
If you're using storyboard segues (generally, you should), the prepareForSegue:sender: method is typically where you do this — you get a reference to the new view controller that's about to be shown, and use some function or property you've defined on that view controller to pass it some context. It often looks something like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == mySegueIdentifier {
guard let destination = segue.destinationViewController as? MyViewControllerClass
else { abort("unexpected storyboard segue") }
destination.someProperty = someValue
}
}
You can find good examples of this when you create a new Xcode project with the Master-Detail App template.
tvOS is more similar to iOS than it is to watchOS, although they all have some similarities. In tvOS (like in iOS) you can use both performSegueWithIdentifier:sender: or presentViewController:animated:completion: depending on your situation.
For more on this, you can check out the UIViewController class reference.
I have a collection view with a standard horizontal layout. Upon presenting a view controller and then dismissing it, the collection view reset focus back to the first cell, even though the last focused cell was not that one.
I've set
self.collectionView.remembersLastFocusedIndexPath = YES;
What's weird is that this only happens when I push a view controller on my navigation controller.
So if I do
[self.navigationController pushViewController:controller animated:YES];
and then dismiss, remembersLastFocusedIndexPath does not work properly.
However, if I:
[self presentViewController:controller animated:YES completion:nil];
Then it works as expected.
Any idea why it wouldn't work via a navigation controller?
What worked for me was ensuring that preferredFocusView on the viewController was the collection view:
override weak var preferredFocusedView: UIView? {
return collectionView
}
After this remembersLastFocusedIndexPath seems to work.
make sure that you don't reloadData() on viewWillAppear of your collectionView or TableView. Otherwise rememberedFocus will be reseted to default (for collection view in my case it was the centre visible cell)
By default, the system remembers the position. You should probably fix this issue by setting remembersLastFocusedIndexPath to false.
You could also implement the UICollectionViewDelegate method:
func indexPathForPreferredFocusedViewInCollectionView(collectionView: UICollectionView) -> NSIndexPath?
The documentation tells us:
The functionality of this delegate method is equivalent to overriding the UICollectionView class’s preferredFocusedView method in the UIFocusEnvironment protocol. If the collection view’s remembersLastFocusedIndexPath method is set to YES, this method defines the index path that gets focused when the collection view is focused for the first time.
But for your issue, setting remembersLastFocusedIndexPath to false should fix it.
You can also try to implement similar behaviour. Just remember last focused cell in
func collectionView(collectionView: UICollectionView, didUpdateFocusInContext context: UICollectionViewFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator)
and use this information in
func collectionView(collectionView: UICollectionView, canFocusItemAtIndexPath indexPath: NSIndexPath) -> Bool
For 2022, this is still a severe Apple bug.
Simply does not work:
navigationController?.pushViewController(details, animated: false)
Works perfectly:
present(details, animated: false)
There are no workarounds, currently:
It seems in the far past some workarounds would make it work. There are no workarounds presently. It is just "utterly broken".
Unfortunately, all you can do is file it under the usual heading "You must be joking, Apple."
Context of Problem:
I am trying to learn how to use the storyboard in Xcode 6 by placing three UIViews in the storyboard's topmost viewcontroller. One of the UIView's is blue, one is red, and one is yellow. I am trying to see if I can alter the UIView's programmatically by changing the background color of one of the UIViews. By Command + clicking the UIViewController class and looking at the appropriate functions to override, I've determined "viewDidAppear" is the final function to be called by the UIViewController in its setup code.
Problem:
Here is my override of the function:
override func viewDidAppear(animated: Bool) {
self.bottomView!.backgroundColor = UIColor.blackColor()
super.viewDidAppear(animated)
}
However, when I run this code the the screen with the three UIViews appears for a split second, with NO black-colored UIView, and then proceeds to crash with a
"fatal error: unexpectedly found nil while unwrapping an Optional value"
Question:
What function do I have to override in order to be able to programmatically change the properties of the UIView? What is the best-practices method of doing so?
EDIT:
I am using Storyboard so my three UIView's are declared in the beginning of my UIViewController class as such:
#IBOutlet var topLeftView: UIView?
#IBOutlet var topRightView: UIView?
#IBOutlet var bottomView: UIView?
It seems that none of the views are actually initialized when viewDidLoad gets called, because the result of the following line of code
println("\(bottomView?)")
is "nil". How come this isn't getting initialized?
This is best done in viewDidLoad. viewDidAppear is called when the view is added into the view hierarchy, but it was not yet initiated. viewDidLoad is called when the view is allocated into memory. According to the docs for viewDidLoad:
You usually override this method to perform additional initialization on views that were loaded from nib files
You should override viewDidLoad and modify your UIView's in there.