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.
Related
I use UIView as alert view in my app, and i want to show it as banner on top of screen, when device is not connected to internet. So my issue that this view appears under my nav bar, how can i bring it to front ? I've tried to us UIApplication.shared.keyWindow! and add my backgroundView as subview to it, but it causes other issues.
This is my alert view class: I'll provide all class, but my realisation is in show() method.
import Foundation
import UIKit
import SnapKit
class ConnectionAlertView: UIView, UIGestureRecognizerDelegate {
internal var backgroundView: UIView = {
let view = UIView()
view.backgroundColor = Theme.Color.alertLabelBackgroundColor
view.alpha = 0
view.layer.cornerRadius = 15
return view
}()
internal var dismissButton: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "close_icon")
imageView.layer.cornerRadius = 15
imageView.isUserInteractionEnabled = true
return imageView
}()
internal var descriptionTitleLabel: UILabel = {
let label = UILabel()
label.text = "Відсутнє підключення до Інтернету"
label.font = Theme.Font.fontBodyLarge
label.textColor = .white
return label
}()
internal var descriptionLabel: UILabel = {
let label = UILabel()
label.text = "Перевірте налаштування мережі"
label.font = Theme.Font.fontBodyMedium
label.textColor = .white
return label
}()
// MARK: - Private Methods -
internal func layout() {
backgroundView.addSubview(descriptionTitleLabel)
backgroundView.addSubview(descriptionLabel)
backgroundView.addSubview(dismissButton)
descriptionTitleLabel.snp.makeConstraints { make in
make.trailing.equalTo(backgroundView).offset(54)
make.leading.equalTo(backgroundView).offset(16)
make.top.equalTo(backgroundView).offset(12)
}
descriptionLabel.snp.makeConstraints { make in
make.leading.equalTo(descriptionTitleLabel.snp.leading)
make.top.equalTo(descriptionTitleLabel.snp.bottom).offset(4)
}
dismissButton.snp.makeConstraints { make in
make.width.height.equalTo(30)
make.centerY.equalTo(backgroundView)
make.trailing.equalTo(backgroundView).offset(-16)
}
}
internal func configure() {
let tap = UITapGestureRecognizer(target: self, action: #selector(dismiss(sender:)))
tap.delegate = self
dismissButton.addGestureRecognizer(tap)
}
// MARK: - Public Methods -
func show(viewController: UIViewController) {
guard let targetView = viewController.view else { return }
backgroundView.frame = CGRect(x: 10, y: 50, width: targetView.frame.width - 20 , height: 67)
targetView.addSubview(targetView)
layout()
configure()
// show view
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
UIView.transition(with: self.backgroundView, duration: 0.6,
options: .transitionCrossDissolve,
animations: {
self.backgroundView.alpha = 1
})
}
// hide view after 5 sec delay
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
UIView.transition(with: self.backgroundView, duration: 1,
options: .transitionCrossDissolve,
animations: {
self.backgroundView.alpha = 0
})
}
}
// MARK: - Objc Methods -
#objc internal func dismiss(sender: UITapGestureRecognizer) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
UIView.transition(with: self.backgroundView, duration: 1,
options: .transitionCrossDissolve,
animations: {
self.backgroundView.alpha = 0
})
}
}
}
My viewController:
class PhoneNumViewController: UIViewController {
let alert = ConnectionAlertView()
private func checkInternetConnection() {
if !NetworkingMonitor.isConnectedToInternet {
log.error("No internet connection!")
alert.show(viewController: self)
}
}
}
Since you have a navigation controller and do not wish to add this view to the window directly, I can offer the following idea which could work.
Your UIViewController is contained with the UINavigationController so if you add the alert to your UIViewController, you will notice it below the UINavigationBar.
You could instead show the alert from your UINavigationController instead with the following changes.
1.
In the func show(viewController: UIViewController) in your class ConnectionAlertView: UIView I changed the following line:
targetView.addSubview(targetView)
to
targetView.addSubview(backgroundView)
This does not directly relate to your issue but seems to be a bug and causes a crash as it seems like you want to add the background view on the target view.
2.
In your class ViewController: UIViewController, when you want to show your alert view, pass the UINavigationController instead like this:
if let navigationController = self.navigationController
{
alert.show(viewController: navigationController)
}
This should give you the desired result I believe (The image and font looks different as I do not have these files but should work fine at your end):
I have a UIActivityIndicatorView so when I try to search in search bar, It will loading first before the data come in. I make the spinner in UIViewController extension for animating and remove the animating from the view. but somehow I can't figure it out why the view I want to remove is not removing, when I try to use break point. the function dismissLoading I create is not being called, why it happen? can someone help me. this is my code
fileprivate var containerView: UIView!
extension UIViewController {
func showLoadingView() {
containerView = UIView(frame: view.bounds)
view.addSubview(containerView)
containerView.backgroundColor = .systemBackground
containerView.alpha = 0.0
UIView.animate(withDuration: 0.25) { containerView.alpha = 0.8 }
let activityIndicator = UIActivityIndicatorView(style: .large)
containerView.addSubview(activityIndicator)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor),
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
activityIndicator.startAnimating()
}
func dismissLoadingView() {
DispatchQueue.main.async {
containerView.removeFromSuperview()
containerView = nil
}
}
}
// This is my PhotosViewController
extension PhotosViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
guard let searchText = searchController.searchBar.text, !searchText.isEmpty else {
dataSource.data.value.removeAll()
viewModel.isSearching.value = false
return
}
photoName = searchText
viewModel.isSearching.value = true
showLoadingView()
if let photoName = photoName {
viewModel.getPhoto(query: photoName) { [weak self] error in
self?.dismissLoadingView()
print(error)
}
}
}
}
You're not seeing it disappear because you're adding a new instance of the container view and activity indicator every time the search query changes. If the query updates a second time before the first query has returned results, the reference to your first containerView will be reassigned to a new view instance.
If you attempt to fix this by checking if containerView == nil before presenting, you will also run into issues if you have multiple search results requests out, one returns, and then the loading view gets dismissed despite there being other requests out.
I'm working on a webView based screen and I want to have an activity indicator to spin while the web content is loading.
Since I have to use the same activity indicator in another screen of the app, I've put the code in static functions in a specific file.
The activity indicator seems to work fine for some web (simple) web pages but I have an issue when I load more complex pages. The activity indicators gets duplicate several times. (See screenshot below)
On the screenshot, the first activity indicator has the correct layout but the one below is darker which implies that several other activity indicators have been overlaid on top of each other. And then they never disappears.
When it comes to code:
I have a webView and two delegate methods controlling the activity indicators.
func webViewDidStartLoad(_ webView: UIWebView) {
DispatchQueue.main.async {
EBUtil.startActivityIndicator(self)
}
}
func webViewDidFinishLoad(_ webView: UIWebView) {
DispatchQueue.main.async {
EBUtil.stopActivityIndicator(self)
}
}
I guess it comes from the fact that webViewDidStartLoad gets called several times. Any idea how I can prevent this behaviour to happen?
Thanks in advance.
Edouard
EDIT:
Here is the full code for my VC.
class NewsDetailsViewController: UIViewController, UIWebViewDelegate {
//MARK: - #IBOUTLETS
#IBOutlet weak var webView: UIWebView!
//MARK: - VARIABLES
var url: String!
//MARK: - APP LIFE CYCLE
override func viewDidLoad() {
super.viewDidLoad()
if url != nil {
let urlToLoad = NSURL(string: url)
webView.loadRequest(NSURLRequest(url: urlToLoad as! URL) as URLRequest)
}
}
func webViewDidStartLoad(_ webView: UIWebView) {
DispatchQueue.main.async {
EBUtil.startActivityIndicator(self)
}
}
func webViewDidFinishLoad(_ webView: UIWebView) {
DispatchQueue.main.async {
EBUtil.stopActivityIndicator(self)
}
}
}
And code for activity indicator start and stop:
static func startActivityIndicator(_ sender: UIViewController) {
print("START ANIMATING")
if let view = sender.view {
activityIndicatorBackground = UIView(frame: CGRect(x: view.center.x - 25, y: view.center.y - 25, width: 50, height: 50))
activityIndicatorBackground.backgroundColor = UIColor.black
activityIndicatorBackground.layer.zPosition = CGFloat(MAXFLOAT-1)
activityIndicatorBackground.alpha = 0.6
activityIndicatorBackground.layer.cornerRadius = 5
view.addSubview(activityIndicatorBackground)
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
activityIndicator.center = view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.white
activityIndicator.layer.zPosition = CGFloat(MAXFLOAT)
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()
}
}
static func stopActivityIndicator(_ sender: UIViewController) {
print("STOP ANIMATING")
activityIndicatorBackground.removeFromSuperview()
activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
try this -
add below line in your viewController class -
var activityIndicator: UIActivityIndicatorView?
And update your startActivityIndicator stopActivityIndicator method like below-
func startActivityIndicator(_ sender: UIViewController) {
print("START ANIMATING")
if let view = sender.view {
if activityIndicator != nil {
activityIndicator?.removeFromSuperview()
}
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
activityIndicator?.center = view.center
activityIndicator?.layer.cornerRadius = 10
activityIndicator?.backgroundColor = UIColor.gray
activityIndicator?.hidesWhenStopped = true
activityIndicator?.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.white
activityIndicator?.layer.zPosition = CGFloat(MAXFLOAT)
view.addSubview(activityIndicator!)
activityIndicator?.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()
}
}
func stopActivityIndicator(_ sender: UIViewController) {
print("STOP ANIMATING")
activityIndicator?.stopAnimating()
activityIndicator?.removeFromSuperview()
UIApplication.shared.endIgnoringInteractionEvents()
}
Hope it will work for you :)
I have came across really cool transitions between viewControllers since UIViewControllerAnimatedTransitioning protocol was made available in IOS 7. Recently I noticed a particularly interesting one in Intacart's IOS app.
Here is the animation I am talking about in slow motion:
https://www.dropbox.com/s/p2hxj45ycq18i3l/Video%20Oct%2015%2C%207%2023%2059%20PM.mov?dl=0
First I thought it was similar to what the author walks through in this tutorial, with some extra fade-in and fade-out animations: http://www.raywenderlich.com/113845/ios-animation-tutorial-custom-view-controller-presentation-transitions
But then if you look at it closely, it seems like the product image transitions between the two viewControllers as the first viewController fades out. The reason why I think there are two viewControllers is because when you swipe the new view down, you can still see the original view behind it with no layout changes.
Maybe two viewControllers actually have the product image (not faded out) and are somehow animating at the same time with perfect precision and one of them fades in as the other fades out.
What do you think is actually going on there?
How is it possible to program such a transition animation that it looks like an image is shared between two viewControllers?
Here is what we did in order to achieve floating screenshot of the view during animated transition (Swift 4):
Idea behind:
Source and destination view controllers conforms to InterViewAnimatable protocol. We are using this protocol to find source and destination views.
Then we creating snapshots of those views and hiding them. Instead, at the same position snapshots are shown.
Then we animating snapshots.
At the end of transition we unhiding destination view.
As result it looks like source view is flying to destination.
File: InterViewAnimation.swift
// TODO: In case of multiple views, add another property which will return some unique string (identifier).
protocol InterViewAnimatable {
var targetView: UIView { get }
}
class InterViewAnimation: NSObject {
var transitionDuration: TimeInterval = 0.25
var isPresenting: Bool = false
}
extension InterViewAnimation: UIViewControllerAnimatedTransitioning {
func transitionDuration(using: UIViewControllerContextTransitioning?) -> TimeInterval {
return transitionDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard
let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to) else {
transitionContext.completeTransition(false)
return
}
guard let fromTargetView = targetView(in: fromVC), let toTargetView = targetView(in: toVC) else {
transitionContext.completeTransition(false)
return
}
guard let fromImage = fromTargetView.caSnapshot(), let toImage = toTargetView.caSnapshot() else {
transitionContext.completeTransition(false)
return
}
let fromImageView = ImageView(image: fromImage)
fromImageView.clipsToBounds = true
let toImageView = ImageView(image: toImage)
toImageView.clipsToBounds = true
let startFrame = fromTargetView.frameIn(containerView)
let endFrame = toTargetView.frameIn(containerView)
fromImageView.frame = startFrame
toImageView.frame = startFrame
let cleanupClosure: () -> Void = {
fromTargetView.isHidden = false
toTargetView.isHidden = false
fromImageView.removeFromSuperview()
toImageView.removeFromSuperview()
}
let updateFrameClosure: () -> Void = {
// https://stackoverflow.com/a/27997678/1418981
// In order to have proper layout. Seems mostly needed when presenting.
// For instance during presentation, destination view does'n account navigation bar height.
toVC.view.setNeedsLayout()
toVC.view.layoutIfNeeded()
// Workaround wrong origin due ongoing layout process.
let updatedEndFrame = toTargetView.frameIn(containerView)
let correctedEndFrame = CGRect(origin: updatedEndFrame.origin, size: endFrame.size)
fromImageView.frame = correctedEndFrame
toImageView.frame = correctedEndFrame
}
let alimationBlock: (() -> Void)
let completionBlock: ((Bool) -> Void)
fromTargetView.isHidden = true
toTargetView.isHidden = true
if isPresenting {
guard let toView = transitionContext.view(forKey: .to) else {
transitionContext.completeTransition(false)
assertionFailure()
return
}
containerView.addSubviews(toView, toImageView, fromImageView)
toView.frame = CGRect(origin: .zero, size: containerView.bounds.size)
toView.alpha = 0
alimationBlock = {
toView.alpha = 1
fromImageView.alpha = 0
updateFrameClosure()
}
completionBlock = { _ in
let success = !transitionContext.transitionWasCancelled
if !success {
toView.removeFromSuperview()
}
cleanupClosure()
transitionContext.completeTransition(success)
}
} else {
guard let fromView = transitionContext.view(forKey: .from) else {
transitionContext.completeTransition(false)
assertionFailure()
return
}
containerView.addSubviews(toImageView, fromImageView)
alimationBlock = {
fromView.alpha = 0
fromImageView.alpha = 0
updateFrameClosure()
}
completionBlock = { _ in
let success = !transitionContext.transitionWasCancelled
if success {
fromView.removeFromSuperview()
}
cleanupClosure()
transitionContext.completeTransition(success)
}
}
// TODO: Add more precise animation (i.e. Keyframe)
if isPresenting {
UIView.animate(withDuration: transitionDuration, delay: 0, options: .curveEaseIn,
animations: alimationBlock, completion: completionBlock)
} else {
UIView.animate(withDuration: transitionDuration, delay: 0, options: .curveEaseOut,
animations: alimationBlock, completion: completionBlock)
}
}
}
extension InterViewAnimation {
private func targetView(in viewController: UIViewController) -> UIView? {
if let view = (viewController as? InterViewAnimatable)?.targetView {
return view
}
if let nc = viewController as? UINavigationController, let vc = nc.topViewController,
let view = (vc as? InterViewAnimatable)?.targetView {
return view
}
return nil
}
}
Usage:
let sampleImage = UIImage(data: try! Data(contentsOf: URL(string: "https://placekitten.com/320/240")!))
class InterViewAnimationMasterViewController: StackViewController {
private lazy var topView = View().autolayoutView()
private lazy var middleView = View().autolayoutView()
private lazy var bottomView = View().autolayoutView()
private lazy var imageView = ImageView(image: sampleImage).autolayoutView()
private lazy var actionButton = Button().autolayoutView()
override func setupHandlers() {
actionButton.setTouchUpInsideHandler { [weak self] in
let vc = InterViewAnimationDetailsViewController()
let nc = UINavigationController(rootViewController: vc)
nc.modalPresentationStyle = .custom
nc.transitioningDelegate = self
vc.handleClose = { [weak self] in
self?.dismissAnimated()
}
// Workaround for not up to date laout during animated transition.
nc.view.setNeedsLayout()
nc.view.layoutIfNeeded()
vc.view.setNeedsLayout()
vc.view.layoutIfNeeded()
self?.presentAnimated(nc)
}
}
override func setupUI() {
stackView.addArrangedSubviews(topView, middleView, bottomView)
stackView.distribution = .fillEqually
bottomView.addSubviews(imageView, actionButton)
topView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
middleView.backgroundColor = UIColor.green.withAlphaComponent(0.5)
bottomView.layoutMargins = UIEdgeInsets(horizontal: 15, vertical: 15)
bottomView.backgroundColor = UIColor.yellow.withAlphaComponent(0.5)
actionButton.title = "Tap to perform Transition!"
actionButton.contentEdgeInsets = UIEdgeInsets(dimension: 8)
actionButton.backgroundColor = .magenta
imageView.layer.borderWidth = 2
imageView.layer.borderColor = UIColor.magenta.withAlphaComponent(0.5).cgColor
}
override func setupLayout() {
var constraints = LayoutConstraint.PinInSuperView.atCenter(imageView)
constraints += [
LayoutConstraint.centerX(viewA: bottomView, viewB: actionButton),
LayoutConstraint.pinBottoms(viewA: bottomView, viewB: actionButton)
]
let size = sampleImage?.size.scale(by: 0.5) ?? CGSize()
constraints += LayoutConstraint.constrainSize(view: imageView, size: size)
NSLayoutConstraint.activate(constraints)
}
}
extension InterViewAnimationMasterViewController: InterViewAnimatable {
var targetView: UIView {
return imageView
}
}
extension InterViewAnimationMasterViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = InterViewAnimation()
animator.isPresenting = true
return animator
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = InterViewAnimation()
animator.isPresenting = false
return animator
}
}
class InterViewAnimationDetailsViewController: StackViewController {
var handleClose: (() -> Void)?
private lazy var imageView = ImageView(image: sampleImage).autolayoutView()
private lazy var bottomView = View().autolayoutView()
override func setupUI() {
stackView.addArrangedSubviews(imageView, bottomView)
stackView.distribution = .fillEqually
imageView.contentMode = .scaleAspectFit
imageView.layer.borderWidth = 2
imageView.layer.borderColor = UIColor.purple.withAlphaComponent(0.5).cgColor
bottomView.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
navigationItem.leftBarButtonItem = BarButtonItem(barButtonSystemItem: .done) { [weak self] in
self?.handleClose?()
}
}
}
extension InterViewAnimationDetailsViewController: InterViewAnimatable {
var targetView: UIView {
return imageView
}
}
Reusable extensions:
extension UIView {
// https://medium.com/#joesusnick/a-uiview-extension-that-will-teach-you-an-important-lesson-about-frames-cefe1e4beb0b
public func frameIn(_ view: UIView?) -> CGRect {
if let superview = superview {
return superview.convert(frame, to: view)
}
return frame
}
}
extension UIView {
/// The method drawViewHierarchyInRect:afterScreenUpdates: performs its operations on the GPU as much as possible
/// In comparison, the method renderInContext: performs its operations inside of your app’s address space and does
/// not use the GPU based process for performing the work.
/// https://stackoverflow.com/a/25704861/1418981
public func caSnapshot(scale: CGFloat = 0, isOpaque: Bool = false) -> UIImage? {
var isSuccess = false
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, scale)
if let context = UIGraphicsGetCurrentContext() {
layer.render(in: context)
isSuccess = true
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return isSuccess ? image : nil
}
}
Result (gif animation):
It's probably two different views and an animated snapshot view. In fact, this is exactly why snapshot views were invented.
That's how I do it in my app. Watch the movement of the red rectangle as the presented view slides up and down:
It looks like the red view is leaving the first view controller and entering the second view controller, but it's just an illusion. If you have a custom transition animation, you can add extra views during the transition. So I create a snapshot view that looks just like the first view, hide the real first view, and move the snapshot view — and then remove the snapshot view and show the real second view.
Same thing here (such a good trick that I use it in a lot of apps): it looks like the title has come loose from the first view controller table view cell and slid up to into the second view controller, but it's just a snapshot view:
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?