I created a UIViewController having a view with two UIWebViews. My structure looks like this (I also use auto layout with SnapKit):
class MyIntViewController: UIViewController, UIWebViewDelegate {
private var scrollView: UIScrollView!
private var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor()
setupView()
}
private func setupView() {
scrollView = UIScrollView()
self.view.addSubview(scrollView)
scrollView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(self.view)
}
contentView = UIView()
contentView.backgroundColor = UIColor.greenColor()
scrollView.addSubview(contentView)
contentView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(self.scrollView)
make.width.equalTo(self.view.bounds.width)
}
// first webView
let contentWebView1 = UIWebView()
contentWebView1.scalesPageToFit = false
contentWebView1.delegate = self
contentWebView1.scrollView.bounces = false
contentWebView1.scrollView.scrollEnabled = false
contentView.addSubview(contentWebView1)
contentWebView1.snp_makeConstraints { (make) -> Void in
make.top.equalTo(contentView.snp_top)
make.left.equalTo(self.view.snp_left)
make.right.equalTo(self.view.snp_right)
make.height.equalTo(1)
}
// second webView
let contentWebView2 = UIWebView()
contentWebView2.scalesPageToFit = false
contentWebView2.delegate = self
contentWebView2.scrollView.bounces = false
contentWebView2.scrollView.scrollEnabled = false
contentView.addSubview(contentWebView2)
contentWebView2.snp_makeConstraints { (make) -> Void in
make.top.equalTo(contentWebView1.snp_bottom)
make.left.equalTo(self.view.snp_left)
make.right.equalTo(self.view.snp_right)
make.height.equalTo(1)
make.bottom.equalTo(self.contentView.snp_bottom)
}
// load content for contentWebView1 and contentWebView2
let path: String = NSBundle.mainBundle().bundlePath
let baseURL: NSURL = NSURL.fileURLWithPath(path)!
let myFileHtml: String = "HTWML for contentWebView1""
let myFileHtmlPlusCss = MMCssHelper.appendCustomCSS(myFileHtml)
contentWebView1.loadHTMLString(myFileHtmlPlusCss, baseURL: baseURL)
let path: String = NSBundle.mainBundle().bundlePath
let baseURL: NSURL = NSURL.fileURLWithPath(path)!
let myFileHtml: String = "HTWML for contentWebView2""
let myFileHtmlPlusCss = MMCssHelper.appendCustomCSS(myFileHtml)
contentWebView2.loadHTMLString(myFileHtmlPlusCss, baseURL: baseURL)
}
// MARK: UIWebViewDelegate
func webViewDidFinishLoad(webView: UIWebView) {
let webViewContentHeight: CGFloat = webView.scrollView.contentSize.height
println("webViewDidFinishLoad webViewContentHeight: \(webViewContentHeight)")
webView.snp_updateConstraints { (make) -> Void in
make.height.equalTo(webViewContentHeight)
}
}
}
When I have only one UIWebView on the page everything works fine the content of the UIWebView is calculated correctly in webViewDidFinishLoad. The problem occurs with two UIViewViews as shown above. The console output is this:
webViewDidFinishLoad webViewContentHeight: 1.0
webViewDidFinishLoad webViewContentHeight: 17.0
You can see that one webViewContentHeight is calculated correct while the other seems to be not calculated at all.
How do I get the height of a UIWebView calculated correctly when there are more than one UIWebViews on the page?
Related
I want to start addArrangedSubview from bottom alignment as like as attached 2nd screenshot(3rd is my actual working screenshot). But every time it arranged from top to bottom. But I need it from bottom to top arrange. I want to create this design using UIStackView inside UIScrollView. I'm trying it with UIScrollView because of landscapes orientation support. And UIStackView for better efficiency with native view.
https://github.com/amitcse6/BottomAlignStackView
import UIKit
import SnapKit
class ViewController: UIViewController {
private var storeBack: UIView?
private var myImageView: UIImageView?
private var myScrollView: UIScrollView?
private var myStackView: UIStackView?
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor=UIColor.white
self.loadStoreBack()
self.loadBackgroundImage()
self.loadScrollView()
self.loadStackView()
self.loadUI()
}
func loadStoreBack() {
self.storeBack=UIView()
self.view.addSubview(self.storeBack!)
self.storeBack?.backgroundColor=UIColor.white
self.storeBack?.snp.remakeConstraints { (make) in
make.top.equalTo(self.view!.snp.top)
make.left.equalTo(self.view.snp.left)
make.right.equalTo(self.view.snp.right)
make.bottom.equalTo(self.view.snp.bottom)
}
}
func loadBackgroundImage() -> Void {
self.myImageView = UIImageView()
self.storeBack?.addSubview(self.myImageView!)
self.myImageView?.contentMode = .scaleAspectFill
self.myImageView?.image=UIImage(named: "welcome-background")
self.myImageView?.snp.remakeConstraints { (make) in
make.top.equalTo(self.storeBack!.snp.top)
make.left.equalTo(self.storeBack!.snp.left)
make.right.equalTo(self.storeBack!.snp.right)
make.bottom.equalTo(self.storeBack!.snp.bottom)
}
}
func loadScrollView() {
self.myScrollView = UIScrollView()
self.storeBack?.addSubview(self.myScrollView!)
self.myScrollView?.backgroundColor=UIColor.clear
self.myScrollView?.showsHorizontalScrollIndicator = false
self.myScrollView?.showsVerticalScrollIndicator = false
self.myScrollView?.bounces=false
self.myScrollView?.isScrollEnabled=true
self.myScrollView?.snp.remakeConstraints { (make) in
make.top.equalTo(self.storeBack!.snp.top)
make.left.equalTo(self.storeBack!.snp.left)
make.right.equalTo(self.storeBack!.snp.right)
make.bottom.equalTo(self.storeBack!.snp.bottom)
make.width.equalTo(self.storeBack!)
make.height.equalTo(self.storeBack!)
}
}
func loadStackView() {
self.myStackView = UIStackView()
self.myScrollView?.addSubview(self.myStackView!)
self.myStackView?.backgroundColor=UIColor.clear
self.myStackView?.axis = .vertical
self.myStackView?.spacing = 0
//self.myStackView?.alignment = .bottom
self.myStackView?.snp.remakeConstraints { (make) in
make.top.equalTo(self.myScrollView!.snp.top)
make.left.equalTo(self.myScrollView!.snp.left)
make.right.equalTo(self.myScrollView!.snp.right)
make.bottom.equalTo(self.myScrollView!.snp.bottom)
make.width.equalTo(self.myScrollView!)
make.height.equalTo(self.myScrollView!).priority(250)
}
}
func loadUI() {
for n in 0..<5 {
if n%2 == 0 {
loadBuddyLogoImageView1()
}else{
loadBuddyLogoImageView2()
}
}
}
func loadBuddyLogoImageView1() {
let subBackView=UIView()
self.myStackView?.addArrangedSubview(subBackView)
subBackView.backgroundColor=UIColor.clear
let backgroundView=UIView()
subBackView.addSubview(backgroundView)
backgroundView.backgroundColor=UIColor.red
backgroundView.snp.remakeConstraints { (make) in
make.top.equalTo(subBackView.snp.top)
make.left.equalTo(subBackView.snp.left)
make.right.equalTo(subBackView.snp.right)
make.bottom.equalTo(subBackView.snp.bottom)
make.height.equalTo(100)
}
}
func loadBuddyLogoImageView2() {
let subBackView=UIView()
self.myStackView?.addArrangedSubview(subBackView)
subBackView.backgroundColor=UIColor.clear
let backgroundView=UIView()
subBackView.addSubview(backgroundView)
backgroundView.backgroundColor=UIColor.green
backgroundView.snp.remakeConstraints { (make) in
make.top.equalTo(subBackView.snp.top)
make.left.equalTo(subBackView.snp.left)
make.right.equalTo(subBackView.snp.right)
make.bottom.equalTo(subBackView.snp.bottom)
make.height.equalTo(100)
}
}
}
I just recently migrated my iOS UIWebview objective-c to WKWebkit swift, the problem am facing now is how to hide the scroll bar from the website I loaded. I have tried anything but not of it work, please can anyone help me out.
I have followed this step here How to hide scrollbar in WebView?, both the question and accepted answer but it didn't work for me.
Please I know this might be a duplicate question but have I have tried many post to solve this none work.
var lastOffsetY :CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
webViewSetup()
}
In webViewSetup
func webViewSetup(){
webView.scrollView.delegate = self
}
In viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let scrollView = webView.subviews[0] as? UIScrollView
webView.scrollView.contentSize = CGSize(width: webView.frame.size.width, height: webView.scrollView.contentSize.height)
scrollView?.bounces = false
scrollView?.decelerationRate = .fast
scrollView?.showsHorizontalScrollIndicator = false
webView.scrollView.showsHorizontalScrollIndicator = false
webView.scrollView.showsVerticalScrollIndicator = false
webView.scrollView.alwaysBounceHorizontal = false
webView.scrollView.bounces = false
}
In scrollViewDidScroll
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.size.height) {
scrollView.setContentOffset(CGPoint(x:scrollView.contentOffset.x, y:scrollView.contentSize.height - scrollView.frame.size.height), animated: false)
}
}
//FIXING SCROLL VIEW
//Delegate Methods
func scrollViewWillBeginDragging(_ scrollView: UIScrollView){
lastOffsetY = scrollView.contentOffset.y
}
//FIXING SCROLL VIEW
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView){
let hide = scrollView.contentOffset.y > self.lastOffsetY
self.navigationController?.setNavigationBarHidden(hide, animated: true)
}
To turn off the web view's scrolling:
webView.scrollView.isScrollEnabled = false
If the code you provided is only for turning off the scrollview, that code can largely be culled down to something like this:
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate {
var webView: WKWebView!
//configure webView
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
webView.scrollView.isScrollEnabled = false
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
if let url = URL(string: "https://www.stackoverflow.com") {
webView.load(URLRequest(url: url))
}
}
}
I load the progress loading animation and when the response from Alamofire comes I use part of the response to construct the full url I need to load in the wkwebview and then I trigger webview.load(..).
My problem is that the progress loading animation gets stuck as soon as webview.load(..) starts to happen and remain stuck till I hide() it.
How can I actually have my animation to keep moving meanwhile the webview starts loading the page?
MyViewController.swift
class MyViewController: UIViewController, WKScriptMessageHandler {
var webView: WKWebView?
#IBOutlet weak var webViewContainer: UIView!
var webConfig:WKWebViewConfiguration {
get {
let webCfg:WKWebViewConfiguration = WKWebViewConfiguration()
let userController:WKUserContentController = WKUserContentController()
userController.add(self, name: "mycontroller")
webCfg.userContentController = userController;
return webCfg;
}
}
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView (frame: webViewContainer.bounds, configuration: webConfig)
webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView?.scrollView.isScrollEnabled = false
webViewContainer.addSubview(webView!)
loadWebview()
}
func loadWebview(){
Loading.shared.show(self.view)
Alamofire.request(MYAPI, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: nil)
.responseJSON { response in
let url = URL(string: "https://path-to-load/\(response.key)")
self.webView!.load(URLRequest(url: url!))
}
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
let message = message.body as! [String:AnyObject]
let event = message["event"] as? String ?? "empty"
switch (event){
case "loading-finished":
DispatchQueue.main.async {
Loading.shared.hide(animated: true)
}
break
default:
break
}
}
}
Loading.swift
public class Loading {
var blurredEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
let imageView = UIImageView(image: UIImage(named: "loading_image"))
class var shared: Loading {
struct LoadingStatic {
static let instance: Loading = Loading()
}
return LoadingStatic.instance
}
init() {
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
imageView.contentMode = .scaleToFill
blurredEffectView.contentView.addSubview(imageView)
}
public func show(_ view: UIView, inView: Bool = false) {
var window: UIView!
if inView == false, let w = view.window {
window = w
} else {
window = view
}
if blurredEffectView.superview == window {
return
}
let rotation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = NSNumber(value: Double.pi * 2)
rotation.duration = 1
rotation.isCumulative = true
rotation.repeatCount = Float.greatestFiniteMagnitude
imageView.layer.add(rotation, forKey: "rotationAnimation")
imageView.center = window.center
blurredEffectView.frame = window.bounds
window.addSubview(blurredEffectView)
blurredEffectView.fadeIn()
}
}
Possible solutions:
1) Make the loading happens in Application Window when you want a full screen Loading (inview == false by default) and keep loadWebview() in viewDidLoad
public func show(_ view: UIView? = nil, inView: Bool = false) {
var window: UIView!
if inView == false, let w = UIApplication.shared.delegate?.window {
window = w
} else {
window = view
}
...
2) Move loadWebview() from viewDidLoad to viewDidAppear
The important part that is moving here is Loading.shared.show(self.view). I wasn't able to animate the components of my view until it has finished laying out (which happens exactly in viewDidAppear()).
More details: viewDidLoad is called after the MyViewController has been initialised and has initialised the main view but before the view is presented to the UI. This means that viewDidLoad allows me to setup the ViewController and the View, before it is shown to the user for interactions.
Although it was a nice and quick solution, in some situation this may not work as expected as viewDidAppear can be called twice, and hence showing the loading view twice which will result in a weird ux.
I have created a custom View Class that inherits from GADNativeContentAdView Class. When I receive an advertisement and the delegate is called, I fill my custom view with the data as shown below.
Everything looks fine but the problem is that it is not clickable at all. I tried to set the actionbutton userinteraction to false, but still won't work.
I also tried to register using following:
-(void)registerAdView:(UIView *)adView
clickableAssetViews:(NSDictionary *)clickableAssetViews
nonclickableAssetViews:
(NSDictionary *)nonclickableAssetViews;
Any idea how to get it to work?
- (void)setNativeContent:(GADNativeContentAd *)nativeContent
{
self.nativeContentAd = nativeContent;
headlineLabel.text = nativeContent.headline;
bodyLabel.text = nativeContent.body;
advertiserImage.image = ((GADNativeAdImage *)nativeContent.images.firstObject).image;
[actionButton setTitle:nativeContent.callToAction forState:UIControlStateNormal];
if (nativeContent.logo && nativeContent.logo.image)
{
advertiserLogo.image = nativeContent.logo.image;
}
else
{
advertiserLogo.image = advertiserImage.image;
}
NSDictionary *clickableArea = #{GADNativeContentHeadlineAsset:headlineLabel, GADNativeContentImageAsset:advertiserImage, GADNativeContentCallToActionAsset:actionButton};
NSDictionary *nonClickableArea = #{GADNativeContentBodyAsset:bodyLabel};
[nativeContent registerAdView:self clickableAssetViews:clickableArea nonclickableAssetViews:nonClickableArea];
}
I finally figured out a way to make the entire native ad clickable without using a .xib. I subclassed GADNativeContentAdView and created a tappableOverlay view that I assigned to an unused asset view in its superclass. In this case, it was the callToActionView. Then I used the not-so-documented GADNativeContentAd.registerAdView() method:
- (void)registerAdView:(UIView *)adView
clickableAssetViews:(NSDictionary<GADNativeContentAdAssetID, UIView *> *)clickableAssetViews
nonclickableAssetViews: (NSDictionary<GADNativeContentAdAssetID, UIView *> *)nonclickableAssetViews;
Here's a Swift 4 example:
class NativeContentAdView: GADNativeContentAdView {
var nativeAdAssets: NativeAdAssets?
private let myImageView: UIImageView = {
let myImageView = UIImageView()
myImageView.translatesAutoresizingMaskIntoConstraints = false
myImageView.contentMode = .scaleAspectFill
myImageView.clipsToBounds = true
return myImageView
}()
private let myHeadlineView: UILabel = {
let myHeadlineView = UILabel()
myHeadlineView.translatesAutoresizingMaskIntoConstraints = false
myHeadlineView.numberOfLines = 0
myHeadlineView.textColor = .black
return myHeadlineView
}()
private let tappableOverlay: UIView = {
let tappableOverlay = UIView()
tappableOverlay.translatesAutoresizingMaskIntoConstraints = false
tappableOverlay.isUserInteractionEnabled = true
return tappableOverlay
}()
private let adAttribution: UILabel = {
let adAttribution = UILabel()
adAttribution.translatesAutoresizingMaskIntoConstraints = false
adAttribution.text = "Ad"
adAttribution.textColor = .white
adAttribution.textAlignment = .center
adAttribution.backgroundColor = UIColor(red: 1, green: 0.8, blue: 0.4, alpha: 1)
adAttribution.font = UIFont.systemFont(ofSize: 11, weight: UIFont.Weight.semibold)
return adAttribution
}()
override var nativeContentAd: GADNativeContentAd? {
didSet {
if let nativeContentAd = nativeContentAd, let callToActionView = callToActionView {
nativeContentAd.register(self,
clickableAssetViews: [GADNativeContentAdAssetID.callToActionAsset: callToActionView],
nonclickableAssetViews: [:])
}
}
}
init() {
super.init(frame: CGRect.zero)
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .white
isUserInteractionEnabled = true
callToActionView = tappableOverlay
headlineView = myHeadlineView
imageView = myImageView
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
addSubview(myHeadlineView)
addSubview(myImageView)
addSubview(adAttribution)
addSubview(tappableOverlay)
}
// override func updateConstraints() {
// ....
// }
}
Just be sure to pin the tappableOverlay to its superview edges so that they're the same size...in updateConstraints().
Inside the method simply you can create and place Ad in view hierarchy.
GADNativeContentAdView *contentAdView = [[NSBundle mainBundle] loadNibNamed:#"NativeAdView" owner:nil options:nil].firstObject;
After assigning the properties, associate the content Ad view with the content ad object. This is required to make the ad clickable.
contentAdView.nativeContentAd = nativeContentAd;
Only AdMob whitelisted publishers can use the registerAdView API :)
All publishers can use xib to create an ad view.
Don't forget to link custom GADUnifiedNativeAdView outlets to your UILabels, UIButtons and ImageViews, so GADUnifiedNativeAdView will know what to interact with
In my case it was cause I created my views without xib.
In this case just set mediaView property to your GADNativeAdView
here the minimum working code
final class EndBannerController: UIViewController {
private let adId: String
private let adView = GADNativeAdView()
private let mediaView = GADMediaView()
private var adLoader: GADAdLoader?
init(adId: String) {
self.adId = adId
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { return nil }
override func viewDidLoad() {
super.viewDidLoad()
adView.frame = view.bounds
view.addSubview(adView)
mediaView.frame = view.bounds
adView.mediaView = mediaView
adView.addSubview(mediaView)
let loader = GADAdLoader(
adUnitID: adId,
rootViewController: self,
adTypes: [.native],
options: nil
)
loader.delegate = self
self.adLoader = loader
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.loadBannerAd()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
adView.frame = view.bounds
mediaView.frame = view.bounds
}
private func loadBannerAd() {
let request = GADRequest()
request.scene = view.window?.windowScene
self.adLoader?.load(request)
}
}
I'm trying to update text of a label after a scroll event. I have a print command that prints the correct value but the label is not updating.
Here's my code
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let x = scrollView.contentOffset.x
let w = scrollView.bounds.size.width
let p = Int(x/w)
print("page \(p)") // this prints correct value
self.signalLabel.text = signalText[Int(x/w)] // this does not update
}
what's the deal?
Here's the complete view controller code. This view is called from a button click on the initial view controller. This view contains a UIScrollView and UIPageControl. The UIScrollView contains two images that can be scrolled back and forth. I want to update the label text based on image that is shown.
import UIKit
class SignalOneViewController: UIViewController, UIScrollViewDelegate {
// MARK: Properties
#IBOutlet weak var signalScrollView: UIScrollView!
#IBOutlet weak var signalPageControl: UIPageControl!
#IBOutlet weak var signalLabel: UILabel!
// MARK: - Button Actions
#IBAction func signalOneButton(_ sender: Any) {
print("signal one button clicked")
performSegue(withIdentifier: "SignalOneSegue", sender: self)
}
#IBAction func onCancelButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
let signalImages = ["signal1a.png", "signal1b.png"]
let signalText = ["Ready for play", "Untimed down"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidLayoutSubviews() {
self.loadScrollView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadScrollView() {
let pageCount : CGFloat = CGFloat(signalImages.count)
signalLabel.text = signalText[0]
signalScrollView.backgroundColor = UIColor.clear
signalScrollView.delegate = self
signalScrollView.isPagingEnabled = true
signalScrollView.contentSize = CGSize(width: signalScrollView.frame.size.width * pageCount, height: signalScrollView.frame.size.height)
signalScrollView.showsHorizontalScrollIndicator = false
signalScrollView.showsVerticalScrollIndicator = false
signalPageControl.numberOfPages = Int(pageCount)
signalPageControl.pageIndicatorTintColor = UIColor.lightGray
signalPageControl.currentPageIndicatorTintColor = UIColor.blue
signalPageControl.addTarget(self, action: #selector(self.pageChanged), for: .valueChanged)
for i in 0..<Int(pageCount) {
print(self.signalScrollView.frame.size.width)
let image = UIImageView(frame: CGRect(x: self.signalScrollView.frame.size.width * CGFloat(i), y: 0, width: self.signalScrollView.frame.size.width, height: self.signalScrollView.frame.size.height))
image.image = UIImage(named: signalImages[i])!
image.contentMode = UIViewContentMode.scaleAspectFit
self.signalScrollView.addSubview(image)
}
}
//MARK: UIScrollView Delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let viewWidth: CGFloat = scrollView.frame.size.width
// content offset - tells by how much the scroll view has scrolled.
let pageNumber = floor((scrollView.contentOffset.x - viewWidth / 50) / viewWidth) + 1
signalPageControl.currentPage = Int(pageNumber)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let x = scrollView.contentOffset.x
let w = scrollView.bounds.size.width
let p = Int(x/w)
print("page \(p)")
self.signalLabel.text = signalText[p]
print(">>> \(signalText[Int(x/w)])")
}
//MARK: page tag action
#objc func pageChanged() {
let pageNumber = signalPageControl.currentPage
var frame = signalScrollView.frame
frame.origin.x = frame.size.width * CGFloat(pageNumber)
frame.origin.y = 0
signalScrollView.scrollRectToVisible(frame, animated: true)
}
}
Make sure signalLabe IBOutlet is attached to your label in storyboard or xib