I have embedded a youtube video in a UIWebView for display in my app.
Here's the code for that:
func loadVideo(){
if let youtubeCode = exerciseYoutubeCode {
guard !youtubeCode.isEmpty else { return }
exerciseVideoWebView.isHidden = false
exerciseVideoWebView.allowsInlineMediaPlayback = true
exerciseVideoWebView.scrollView.isScrollEnabled = false
exerciseVideoWebView.scrollView.bounces = false
exerciseVideoWebView.loadHTMLString("<html><head><style>body{margin:0px}</style></head><body><iframe width=\"\(exerciseVideoWebView.frame.width)\" height=\"\(exerciseVideoWebView.frame.height)\" src=\"https://www.youtube.com/embed/\(youtubeCode)?&playsinline=1\" frameborder=\"0\" allowfullscreen></iframe></body></html>",
baseURL: nil)
}
}
The problem is that the embedded video does not seem to be respecting the width and height of the UIWebView that I am passing in to the iframe. In the simulator, the video is roughly 15-25 pixels larger than the UIWebView. I can live with that, but when running on a physical device, it's rendering 15-25 pixels smaller than the UIWebView, exposing the white of the HTML page on the right and bottom sides of the UIWebView.
The UIWebView has an AutoConstraint to a 16:9 ratio, which matches YouTube's video aspect ratio.
If I put the following script into the HTML, I can see that the width and height of the video iframe is an exact match to the height and width printed to the console for the UIWebView:
<script>setTimeout(function(){
alert(document.getElementsByTagName('iframe')[0].offsetWidth + ' ' + document.getElementsByTagName('iframe')[0].offsetHeight)},10000)
</script>
Clearly there is some sort of scaling happening if the HTML thinks its the same size as the UIWebView, but I can't figure out how to fix it.
Related
I have recently inherited an an iOS app made in Swift 3.0 from a developer that no longer works here.
The app is made in Xcode and uses storyboards for some of the screens, but not all of them.
On a iPad in landscape orientation the main screen contains an image taking op 2/3 of the width, with a text column next to it taking up 1/3 of the screen. Below are three images each up 1/3 wide. On smaller screens all these items take up 100% of the available width and are displayed underneath each other as a long list.
This is done using the following code in MainController.swift:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews();
// On small screens, columnify the layout
let mainContent = view.viewWithTag(1) as! UIStackView; //contains large image and text
let thumbnails = view.viewWithTag(3) as! UIStackView; //three images
mainContent.axis = .horizontal
mainContent.distribution = .fillProportionally
thumbnails.axis = .horizontal
if(self.view.bounds.width < 1000) {
mainContent.axis = .vertical
mainContent.distribution = .equalSpacing
thumbnails.axis = .vertical
} else {
mainContent.axis = .horizontal
mainContent.distribution = .fillProportionally
thumbnails.axis = .horizontal
}
}
This all works well, unless the user performs the following actions:
Start in landscape orientation on iPad. Image is 2/3, text 1/3
Tap on thumbnail and navigate to a different panel.
Rotate iPad to portrait.
Navigate back to first panel.
Observe that all items are places underneath each other, the image is 100%, but text is only 1/3 of the screen, while it should be 100%
Rotate to landscape, text moves next to image.
Rotate to portrait, text now spans 100% (as it should be)
My hunch would be that manually triggering a re-layout after navigating back to the first panel would solve it, but I cannot find code related to the navigation. This seems all handled by some "Apple magic?". There is probably a way to hook into it, but without any code I don't have any pointers. My only other solution would be to try and refactor the entire application with storyboards, but before I start with that, I was hoping on getting some insights here.
I have three web view on one screen, and I need the screens to fit the size of each content. How can I solve it?
I already tried using:
let it = productGoal.stringByEvaluatingJavaScript(from: "Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight );")
To get the screen size of the webview content, but it always brings me the current screen size.
This worked for me:
let it = Double(webView.stringByEvaluatingJavaScript(from: "document.documentElement.scrollHeight") ?? "0")
When my WebView is inside a normal UIView every thing works and video plays as expected. (I created a new sample project to test it.)
But when I put the WebView inside a UITableView. Its not playing. It loads the video player with video's thumbnail and big red play button. When I click play button, video won't play. The WebView goes black.
Here's my code
void LoadVideo (string videoId)
{
nfloat width = UIScreen.MainScreen.Bounds.Size.Width - 32;
nfloat height = VideoWebview.Frame.Size.Height;
string embedHtml = "<iframe width=\"{0}\" height=\"{1}\" src=\"https://www.youtube.com/embed/{2}?modestbranding=1&showinfo=0\" frameborder=\"0\" style=\"margin:-8px;padding:0;\" allowfullscreen></iframe>";
string html = string.Format (embedHtml, width, height, videoId);
Console.WriteLine (html);
VideoWebview.ScrollView.Bounces = false;
VideoWebview.LoadHtmlString (html, new Foundation.NSUrl ("https://www.youtube.com"));
}
This works fine when the WebView is not inside a UITableViewCell. What am I doing wrong here? Any help is appreciated.
Edit:
I've found that it's some issue with my project. I created a new sample project and its working inside a UITableViewCell. But in the current project that I'm working it just don't work... not in UITableViewCell, not in UIView.
I have embedded HTML5 video with mp4 format. How to get thumbnail image like poster without using "poster" attribute. This problem coming on Safari and iOS. I have added video like below mentioned code.
<video height=350 id='TestVideo' webkit-playsinline><source src='test.mp4' type=video/mp4></video>
On Chrome, IE, Firefox first frame of video coming as thumbnail image, but not coming on Safari and iOS.
simply add preload="metadata" in video tag and set #t=0.1 at url src, it will get the frame of 0.1s of your video as thumbnail
however, the disadvantage of this solution is when you click to play the video, it always start at 0.1s
<video preload="metadata" controls>
<source src="video.mp4#t=0.1" type="video/mp4">
</video>
If you want to do this without storing server side images it is possible, though a bit clunky... uses a canvas to record the first frame of the video and then overlay that over the video element. It may be possible to use the URL for the Canvas as a source for the Poster (eg video.poster = c.toDataURL();) but that requires correct CORS setup (not sure if the video was on your own server, and if you have control over that, so took the safest option). This will work best if video is correctly encoded for streaming (so MOOV atom is at the front of the video, otherwise it will be slow to draw the overlay - see this answer for more detail)
The HEAD contains styling for the video and the overlay (you will need to adjust sizing and position to suit your application)
<head>
<style>
video_box{
float:left;
}
#video_overlays {
position:absolute;
float:left;
width:640px;
min-height:264px;
background-color: #000000;
z-index:300000;
}
</style>
</head>
In the BODY you will need the div for the overlay and the video. The overlay div has an onclick handler to hide the overlay and start the video playing
<div id="video_box">
<div id="video_overlays" onclick="this.style.display='none';document.getElementById('video').play()"></div>
<video id="video" controls width="640" height="264">
<source src="BigBuck.mp4" type='video/mp4'>
</video>
</div>
</div>
Finally you will need code that will load the video, seek to the first frame and load the visual into a canvas that you then insert into the overlay
<script>
function generateOverlay() {
video.removeEventListener('seeked',generateOverlay); / tidy up the event handler so it doesn't redraw the overlay every time the user manually seeks the video
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
c.width = 640;
c.height = 264;
ctx.drawImage(video, 0, 0, 640, 264); // take the content of the video frame and place in canvas
overlay.appendChild(c); // insert canvas into the overlay div
}
// identify the video and the overlay
var video = document.getElementById("video");
var overlay = document.getElementById("video_overlays");
// add a handler that when the metadata for the video is loaded it then seeks to the first frame
video.addEventListener('loadeddata', function() {
this.currentTime = 0;
}, false);
// add a handler that when correctly seeked, it generated the overlay
video.addEventListener('seeked', function() {
// now video has seeked and current frames will show
// at the time as we expect
generateOverlay();
}, false);
// load the video, which will trigger the event handlers in turn
video.load();
</script>
this is a bit late but we had the same scenario. I can't use the attribute 'muted' because my videos are podcasts. This is what I came up with and I hope to share it with future visitors. What I did is load the video in the body tag, drew a canvas, retrieved as base64 and applied to the video as the background image.
Since my video should be fixed 150px in height, I computed the aspect ratio so that whatever height and width of the actual video, it will be resized into 150px height and dynamic width.
$('body').append('<video class="checkmeload" style="position:absolute;top:-10000px" controls preload="auto" playsinline src="//src-here"><source src="//src-here" type="//videotype-here"></video>');
$('body').find('.checkmeload').on('loadeddata', function(){
var video = $('.checkmeload');
var canvas = document.createElement('canvas');
aspectratio = (video[0].videoWidth / video[0].videoHeight);
newwidth = (150 * aspectratio);
canvas.width = newwidth;
canvas.height = 150;
var context = canvas.getContext('2d');
context.drawImage(video[0], 0, 0, canvas.width, canvas.height);
var dataURI = canvas.toDataURL('image/jpeg');
$('body').find('.checkmeload').remove();
$('#myvideo').css('background-image','url("'+dataURI +'")');
});
Source: How to set the thumbnail image on HTML5 video?
This worked for me:
<img src="thumbnail.png" alt="thumbnail" />
/* code for the video goes here */
Now using jQuery play the video and hide the image as
$("img").on("click", function() {
$(this).hide();
// play the video now..
})
Add #t=0.001 at the end of the video URL.
https://muffinman.io/blog/hack-for-ios-safari-to-display-html-video-thumbnail/
I am having lots of trouble with this setup. So basically I am displaying some labels with variable height then a button and at the end of the view i need a WebView to display some HTML formatted text.
I resize the web view height constraint when the content is loaded as follows.
func webViewDidFinishLoad(webView: UIWebView) {
nContentViewHeight.constant = webView.scrollView.contentSize.height
}
When the view is first loaded, this works perfectly. The scrolling is good and all the web view content is visible.
But when I rotate the device I don't know how to properly resize the web view. I tried loading the content again in
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation)
so that webViewDidFinishLoad would trigger again and resize the web view. But that doesn't work at all because the contentSize of the scrollview inside the web view doesn't change.
Not knowing why I attempted very ugly solution and that is this:
func fitWebAndScrollView(){
let newRect = CGRectMake(nContentWebView.frame.minX, nContentWebView.frame.minY, self.view.frame.width, 10)
let newWebView = UIWebView(frame: newRect)
newWebView.delegate = self
newWebView.tag = -12
newWebView.scrollView.scrollEnabled = false
self.view.addSubview(newWebView)
newWebView.loadHTMLString(contentHtml, baseURL: nil)
}
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
fitWebAndScrollView()
}
func webViewDidFinishLoad(webView: UIWebView) {
nContentViewHeight.constant = webView.scrollView.contentSize.height
if webView.tag == -12 {
webView.removeFromSuperview()
nContentViewHeight.constant *= 1.1 // longer the content is more of it is clipped
}
}
And this sort of works, but in some instance the bottom of the WebView content is clipped as if the inner scrollview content size is calculated incorrectly.
Has anybody dealt with this before? I always assumed that this sort of thing wasn't an extra special use case.
Thank you for your ideas.
Aright so I managed to solve it this way
func fitWebAndScrollView(){
if let strH = nContentWebView.stringByEvaluatingJavaScriptFromString("document.getElementById(\"cnt\").offsetHeight;"),
numH = NSNumberFormatter().numberFromString(strH){
nContentViewHeight.constant = CGFloat(numH)+20
}
}
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
fitWebAndScrollView()
}
func webViewDidFinishLoad(webView: UIWebView) {
fitWebAndScrollView()
}
The problem was actually with the HTML code I was using as a wrapper.
let htmlWrapperString = "<html><head>\n<style type=\"text/css\">body {font-family: \"%#\"; font-size: %#;-webkit-text-size-adjust: none;}</style></head><body><div id=\"cnt\">%#</div></body></html>"
So before I didn't have that wrapping div in the body and when I queried the height of body tag it always returned the whole view port size and not just the text height that was in body.
Now it works reliably.
First of all, if the code is really as shown, you are assigning the width of the content to the height constant. This would definitely cause the issue. (Apparently it is not as shown; please show actual code instead of typing in new "similar" code into the question…)
Second, it is not a very reliable way to detect the content height by only doing it in webViewDidFinishLoad. On some sites it may take a long time before this is actually called but the content may still be usable, and of course any action by the user in the browser may change it, and many websites append more content to the end of the page on the fly (e.g., as the user scrolls down).
Also, I hope you realize that scrollView.contentSize is the size of the content itself (not the visible part), and its height won't change on rotation unless the width is changed in a way that makes the content layout change.
Overall I think you may be trying to do something that UIWebView is simply not suited for. You should only autolayout the size of the webview itself, not have it inside an external UIScrollView (if I read the title correctly and that's what you are doing). Its inner scrollView will then handle the content scrolling for you automatically, instead of conflicting with your external scrollView. You can set yourself up as the delegate of the inner scrollview if you need to react to events from it, etc.
To resize the UIWebView itself on rotation, set up constraints to bind the distance from each of its edges to the surrounding views (or use the simpler autoresizingMask). There should be no need to do anything in didRotateFromInterfaceOrientation (and if you do need manual layout, do it in viewWillLayoutSubviews and/or viewDidLayoutSubviews).