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.
Related
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!
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.
I am working on an application using swift and storyboard layout. Can you please suggest me the way how can I point two different tabs of UITabbarController to same UIViewControllers embedded in UINavigationController.
Xcode UITabBarController: Make Two tabs point to same ViewController does not work because I have many tabs in my UITabbarController and I want to do by using storyboards.
thanks.
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 am trying to use the Google Maps SDK for iOS in a subview of the main view which I created in the storyboard and linked to the view controller via an IBOutlet (I called it extraView, subclassed from UIView). When I follow the steps in the SDK getting started guide, the SDK works just fine, but it uses the uppermost view in the hierarchy (the main view), which I don't want. I want my map to be in a smaller portion of the screen and use the rest of the screen for something else. When I attempt to assign the mapView_ object (see the getting started guide) to self.extraView instead of self.view, the whole screen is black and I get an error in the console output:
"Application windows are expected to have a root view controller at the end of application launch"
Has anyone else figured this out? I can't find anything in the documentation and the sample code Google provides does not use a storyboard.
Here's how...
add a UIView into the view controller where you're working
set it's class to be GMSMapView in the identity inspector.
Then control-drag it to your code as you would for any other outlet.
You can lazily instantiate it in its setter...
- (void) setMapView:(GMSMapView *)mapView {
if (!mapView) {
mapView = [[GMSMapView alloc] initWithFrame:mapView.bounds];
}
_mapView = mapView;
}
To display a map Google's sample code becomes...
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:1.285
longitude:103.848
zoom:12];
self.mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
I solved my problem just removing the loadview code that i took from the example.
Just adding a view as sberley said should works.
just on thing more, on the identity inspector, that attribute that you have to change is class, at least it is on xcode 4.5