How to Mirror(Flip) Video playing using AVPlayer? - swift

I want to Mirror (Flip) video playing using AVPlayer.
Like : MirrorTube :- https://chrome.google.com/webstore/detail/mirrortube/olomckflnlligkboahmaihmeaffjdbfm/related?hl=en
i want to achieve same functionality.
I have tried to change CGAffineTransform but it does not work same.
Thanks in advance!

Here's an example how to flip the player vertically and horizontally by using CGAffineTransform:
PlayerView:
import AVKit
class PlayerView: UIView {
var player: AVPlayer? {
get {
return playerLayer.player
}
set {
playerLayer.player = newValue
}
}
var playerLayer: AVPlayerLayer {
return layer as! AVPlayerLayer
}
// Override UIView property
override static var layerClass: AnyClass {
return AVPlayerLayer.self
}
}
ViewController using the playerView defined in xib/storyboard:
#IBOutlet var playerView: PlayerView!
#IBAction func flipVerticallyBarButtonItemTouched(_ sender: UIBarButtonItem) {
UIView.animate(withDuration: 0.2) { [unowned self] in
self.playerView.transform = self.playerView.transform.concatenating(CGAffineTransform(scaleX: 1, y: -1))
}
}
#IBAction func flipHorizontallyBarButtonItemTouched(_ sender: UIBarButtonItem) {
UIView.animate(withDuration: 0.2) { [unowned self] in
self.playerView.transform = self.playerView.transform.concatenating(CGAffineTransform(scaleX: -1, y: 1))
}
}

Note: For an iOS app in Mac Catalyst (with a AVPlayerController subclass) the root view is AVPlayerView.
But oddly AVPlayerView isn't public on iOS > Mac Catalyst so self.playerView wont work so you cant cast it. You get class not found for 'AVPlayerView'
But when you run the app in Mac Catalyst and inspect it the self.view is an AVPlayerView
workaround just flip the root view itself without casting - self.view
`class MyAVPlayerController: AVPlayerController`
to flip horizontal etc
self.view.transform = self.view.transform.concatenating(CGAffineTransform(scaleX: -1, y: 1))
to flip vertical etc
self.view.transform = self.view.transform.concatenating(CGAffineTransform(scaleX: 1, y: -1))
KNOWN ISSUE this flips the whole AVPlayerView including controls. in iOS 16 the actual player is a view where Swift.type(of:view) is "__AVPlayerLayerView" so you can walk the hierarchy and find the UIView return it then apply the transform to only that subview
func flipHorizontal(){
print("TradAVPlayerViewController.swift flipHorizontal")
if self.player != nil{
//------------------------------------------------------------------
//v1 - flips VC.view which is AVPlayerView
//but also flips TOAST and CONTROLS
//https://developer.apple.com/documentation/uikit/uiview/1622459-transform
//------------------------------------------------------------------
//OK ROTATES VIEW
//self.view.transform = CGAffineTransform(rotationAngle: CGFloat((90 * Double.pi)/180))
//OK FLIP VERTICAL
//self.view.transform = self.view.transform.concatenating(CGAffineTransform(scaleX: 1, y: -1))
//OK FLIPS HORIZONTAL
// self.view.transform = self.view.transform.concatenating(CGAffineTransform(scaleX: -1, y: 1))
//note the flip remains in place as long as self.player isnt recreated so PREV/NEXT will stay flipped
//------------------------------------------------------------------
//ISSUE flipping the whole view also flips the TOAST and SCRUBBER
//------------------------------------------------------------------
//in iOS 15 the player is a class with name "__AVPlayerLayerView"
//find it in the hierarchy and only apply transform to that on
if let rootView = self.view{
let className = "__AVPlayerLayerView"
if let subViewFound
= findSubViewByName(name: className,
view: rootView)
{
print("FOUND className:'\(className)' flipping it")
subViewFound.transform = subViewFound.transform.concatenating(CGAffineTransform(scaleX: -1, y: 1))
}else{
print("NOT FOUND className:'\(className)' CANT FLIP IT - debug hierarchy in 3d and get class name for the AVPlayerLayerView")
}
}else{
logger.error("self.view is nil")
}
print()
}else{
logger.error("self.player is nil")
}
}
func findSubViewByName(name: String, view: UIView) -> UIView?{
var viewFound: UIView? = nil
for subView in view.subviews{
//MUST WRAP IN STRING else types wont match
let typeName = "\(Swift.type(of: subView))"
//print("typeName:\(typeName)")
if typeName == name {
print("SUB VIEW - typeName:\(typeName) - MATCH!!")
viewFound = subView
break
}else{
print("SUB VIEW - typeName:\(typeName) - NO MATCH")
}
//recurse depth first
if let subViewFound = findSubViewByName(name: name, view: subView){
viewFound = subViewFound
break
}else{
//no match in subviewsf
}
}
return viewFound
}

Related

UIViewControllerAnimatedTransitioning doesn't complete on iOS 9/10, but works iOS 11 and later?

I have a custom presentation controller which shows a UIViewController as a "bottom sheet" above the current ViewController, and allows an interactive gesture to dismiss the presented modal. The modal's modalPresentationStyle is set to .current, and the transitioningDelegate is set to my custom class during init(). The current code works perfectly on iOS 11 and 12, but appears to "freeze" the device on iOS 9 and 10.
Here is where the animation takes place. I have confirmed the presentedFrame and dismissedFrame are correct.
extension BottomSheetPresentationManager: UIViewControllerAnimatedTransitioning {
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.25
}
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let key = presenting ? UITransitionContextViewControllerKey.to
: UITransitionContextViewControllerKey.from
let keyV = presenting ? UITransitionContextViewKey.to
: UITransitionContextViewKey.from
let controller = transitionContext.viewController(forKey: key)!
let view = transitionContext.view(forKey: keyV)!
if presenting {
transitionContext.containerView.addSubview(view)
}
let presentedFrame = transitionContext.finalFrame(for: controller)
var dismissedFrame = presentedFrame
switch direction {
case .left:
dismissedFrame.origin.x = -presentedFrame.width
case .right:
dismissedFrame.origin.x = transitionContext.containerView.frame.size.width
case .top:
dismissedFrame.origin.y = -presentedFrame.height
case .bottom:
dismissedFrame.origin.y = transitionContext.containerView.frame.size.height
}
let initialFrame = presenting ? dismissedFrame : presentedFrame
let finalFrame = presenting ? presentedFrame : dismissedFrame
// Runs when the animation finishes
let completionBlock: (Bool) -> Void = { (finished) in
// tell our transitionContext object that we've finished animating
if transitionContext.transitionWasCancelled {
if self.interactive {
transitionContext.cancelInteractiveTransition()
}
transitionContext.completeTransition(false)
} else {
if self.interactive {
finished ? transitionContext.finishInteractiveTransition() : transitionContext.cancelInteractiveTransition()
}
transitionContext.completeTransition(finished)
}
}
// Put what you want to animate here.
let animationBlock: () -> Void = {
view.frame = finalFrame
}
// Set up for the animation
let animationDuration = transitionDuration(using: transitionContext)
view.frame = initialFrame
// Perform a different animation based on whether we're interactive (performing a gesture) or not
if interactive {
// Do a linear animation so we match our dragging with our transition
UIView.animate(withDuration: animationDuration,
delay: 0,
options: .curveLinear,
animations: animationBlock,
completion: completionBlock)
} else {
// Do a spring animation with easing
UIView.animate(withDuration: animationDuration,
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.45,
options: [.curveEaseInOut],
animations: animationBlock,
completion: completionBlock)
}
}
}
After much testing, I have figured out that the completion block on the UIView.animate block never gets called (completionBlock in the code), and in the Capture View Hierarchy view, it actually shows my presented view on top, with what looks like a screenshot of the view (UIVisualEffectView) directly under the presented menu.
What you are seeing there is my bottom menu (the UITableView with cells that you see in front), with a UIVisualEffectView behind it (appears to be a screenshot of the UICollection view behind that), and a UICollectionView that called the present() method for the menu.
What would cause the UIView.animate block not to complete? Is there any reason the behavior would differ between iOS 10 and 11? Any help would be greatly appreciated.

Close partially shown modal view controller by tapping outside of the view controller

I made a custom modal transition by using UIViewControllerAnimatedTransitioning. After the transition has completed, a modally shown view controller fills part of parent view that some part of parent view controller is still visible.
I need to know how to detect tap on outside of shown view controller(or partially shown parent view controller) so I can use it to close the modal. I understand how to add gesture to a view but I cannot find which view to add a gesture to.
I looked through stackoverflow but there aren't example specifically for custom modal transition.
Here is a code for custom transition
class PartialSlideInAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
var direction: SlideAnimationDirectionStyle
var duration: TimeInterval = 0.125
var presentedControllerWidth: CGFloat = 250
let fadeAlpha: CGFloat = 0.5
init(direction: SlideAnimationDirectionStyle) {
self.direction = direction
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return TimeInterval(duration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// Setup Views
guard let presentedFromVC = transitionContext.viewController(forKey: .from) else {transitionContext.completeTransition(false); return }
guard let presentingToVC = transitionContext.viewController(forKey: .to) else {transitionContext.completeTransition(false); return }
let contrainerView = transitionContext.containerView // アニメーションはこのViewで行われる、アニメーションを実装する presenter, presenteeをこの上に載せないとアニメーションが動かない
// Setup Frames
let positionHidden = CGRect(x: contrainerView.frame.width + 1, y: 0, width: presentedControllerWidth, height: contrainerView.frame.height)
let positionShown = CGRect(x: contrainerView.frame.width - presentedControllerWidth, y: 0, width: presentedControllerWidth, height: contrainerView.frame.height)
switch direction {
case .slideIn:
contrainerView.addSubview(presentingToVC.view)
presentingToVC.view.frame = positionHidden
UIView.animate(withDuration: duration, animations: {
presentingToVC.view.frame = positionShown
presentedFromVC.view.alpha = self.fadeAlpha
}, completion: { success in
transitionContext.completeTransition( success )
})
case .slideOut:
contrainerView.addSubview(presentedFromVC.view)
presentedFromVC.view.frame = positionShown
UIView.animate(withDuration: duration, animations: {
presentedFromVC.view.frame = positionHidden
presentingToVC.view.alpha = 1.0
}, completion: { success in
transitionContext.completeTransition( success )
})
}
}
}
Have you tried using UIGestureRecognizer yet? The UIGestureRecognizer can be utilized to recognize tap events, both inside a child view controller and a parent view controller. For swift 3, there should be a handleTapBehind function of the UIRecognizerDelegate that should allow you to do what you're looking for.
See if the what is documented here
See the "Swift 3.1 solution that works in both portrait and landscape" in the linked post.

Detect if user is moving finger left or right (Swift)

This is not Sprite Kit.
If I have a variable like the one below
var value = 0
How am I able to increase the value if the user drags right and decrease if they drag left?
Thanks!
Like Caleb commented, Ray's tutorial is great, but if you want the actual swift example, please check the next example:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
private var value: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blackColor()
let recognizer = UIPanGestureRecognizer(target: self, action: Selector("handleDragging:"))
let inputView = UIView(frame: CGRectMake(0, 0, 100, 100))
inputView.backgroundColor = UIColor.whiteColor()
inputView.userInteractionEnabled = true
inputView.addGestureRecognizer(recognizer)
self.view.addSubview(inputView)
}
func handleDragging(recognizer: UIPanGestureRecognizer) {
if (recognizer.state == .Changed) {
let point = recognizer.velocityInView(recognizer.view?.superview)
if (point.x > 0) {
self.value++;
} else {
self.value--;
}
println(self.value)
}
}
}
You can use the velocityInView method of UIPanGestureRecognizer to determine which direction you're going. It returns a CGPoint, so you can pull out the x and y values as you wish. Positive is right/down, negative is left/up.

Converting UIKit to SceneKit

Trouble converting program from UIKit to SceneKit. Biggest difficulty for me is understanding how delegate file, Tile, synched with array, Board, is set up with SceneKit. It is a simple project. A screenshot: http://imgur.com/9hsv7X5. It displays a 3 x 5 array. User taps an item and it becomes highlighted. Then tap another item, it becomes highlighted, previous item, unhighlighted.
Here is the UIKit project composed of 3 files:
VIEWCONTROLLER
import UIKit
struct BoardLoc {
var x: Int
var y: Int
}
class ViewController: UIViewController, TileDelegate {
var tile: Tile!
override func viewDidLoad() {
super.viewDidLoad()
let scene = Board()
tile.tileDelegate = self
tile.board = scene
}
func getTileAtLoc(tile: Tile, _ boardLoc: BoardLoc) {
tile.boardLoc = boardLoc
}
}
BOARD
import Foundation
class Board {
var board: Array<Array<String>> = Array(count:3, repeatedValue:Array(count:5, repeatedValue:"foo"))
func putTileAt(boardLoc: BoardLoc) -> String {
return board[boardLoc.x][boardLoc.y]
}
}
TILE
import UIKit
protocol TileDelegate {
func getTileAtLoc(tile: Tile, _ boardLoc: BoardLoc)
}
class Tile: UIView {
var boardLoc: BoardLoc?
var board: Board?
var tileDelegate: TileDelegate?
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addGestureRecognizer(UITapGestureRecognizer(target: self, action:"handleTap:"))
}
override func drawRect(rect: CGRect) {
for x in 0...2 {
for y in 0...4 {
let context = UIGraphicsGetCurrentContext()
let red = UIColor.redColor().CGColor
let orange = UIColor.orangeColor().CGColor
let bigCircle = CGRectMake(CGFloat(106 * x),CGFloat(106 * y), 106, 106)
let smallCircle = CGRectMake(CGFloat(106 * x) + 3, CGFloat(106 * y) + 3, 100, 100)
if (boardLoc != nil && boardLoc!.x == x && boardLoc!.y == y) {
CGContextSetFillColorWithColor(context, red)
CGContextFillEllipseInRect(context, bigCircle)
}
if board!.putTileAt(BoardLoc(x: x, y: y)) == "foo" {
CGContextSetFillColorWithColor(context, orange)
CGContextFillEllipseInRect(context, smallCircle)
}
}
}
}
func handleTap(gestureRecognizer: UIGestureRecognizer) {
let point = gestureRecognizer.locationInView(self)
let boardLoc = BoardLoc(x: Int(point.x) / 106, y: Int(point.y) / 106)
tileDelegate!.getTileAtLoc(self, boardLoc)
setNeedsDisplay()
}
}
First of all, I recommend you to read Apple SceneKit document and some tutorials.
Scene Kit is a 3D-rendering Objective-C framework that combines a high-performance rendering engine with a high-level, descriptive API. Scene Kit supports the import, manipulation, and rendering of 3D assets without requiring the exact steps to render a scene the way OpenGL does.
http://www.objc.io/issue-18/scenekit.html
https://www.weheartswift.com/introduction-scenekit-part-1/
http://www.raywenderlich.com/83748/beginning-scene-kit-tutorial
http://tacow.org/assets/attachments/SceneKit.pdf
Scene Kit allows you to render 3D scene easily, without OpenGL ES APIs. However you should understand how Scene Kit works.
Basically, Scene Kit provides a view controller that maintains an animation loop. This loop follows a design pattern common in games and simulations, with two phases: update and render. In the implementation, Scene Kit has more phases like the following figure (from http://www.objc.io/issue-18/scenekit.html), but basically, two phases, update and render.
So how to create Scene Kit project, the basics is
Prepare SCNView
Initialize 3D scene
Create touch event handler
Implement Update phase: Update game board using the touched object or the touched position, Update the animation of the objects, or some sort of stuff.
Implement Render phase: Basically, Scene Kit automatically renders registered 3D objects and models.
Thus, you should implement as the following.
Use SCNView instead of ViewController
Create a scene
Place Board and Tiles as Scene Kit 3D objects
Use hitTest for touching Tile and update Tiles in Update phase
import SceneKit
class GameViewController: UIViewController {
struct BoardLoc {
var x: Int
var y: Int
}
enum Type {
case Yellow
case Orange
}
var boardArray: Array<Array<Type>> = []
override func viewDidLoad() {
super.viewDidLoad()
for x in 0...2 {
boardArray.append(Array(count:5, repeatedValue:Type.Orange))
for y in 0...4 {
boardArray[x][y] = Type.Orange
}
}
let scene = SCNScene(named: "art.scnassets/balls8.dae")
let scnView = self.view as SCNView
scnView.scene = scene
scnView.autoenablesDefaultLighting = true
let taps = NSMutableArray()
let tap = UITapGestureRecognizer(target: self, action: "handleTap:")
taps.addObject(tap)
scnView.gestureRecognizers = taps
}
func handleTap(gestureRecognizer: UIGestureRecognizer) {
let scnView = view as SCNView
let point = gestureRecognizer.locationInView(scnView)
if let hitResults = scnView.hitTest(point, options: nil) {
if hitResults.count > 0 {
let result: AnyObject! = hitResults[0]
if !result.node!.name!.hasPrefix("Orange") {
return
}
let tapLoc = BoardLoc(x: Int(point.x) / 106, y: Int(point.y) / 106)
boardArray[tapLoc.x][tapLoc.y] = Type.Yellow
for col in 0...2 {
for row in 0...4 {
var yellowBall = scnView.scene!.rootNode.childNodeWithName("Yellow", recursively: true)
var secxtorX = Float(col) * 16.5 - 16
var sectorY = 34 - (Float(row) * 16.5)
if boardArray[col][row] == Type.Yellow {
yellowBall!.runAction(SCNAction.moveTo(SCNVector3(x: secxtorX, y: sectorY, z: 25), duration: 0.01))
boardArray[tapLoc.x][tapLoc.y] = Type.Orange
}
}
}
}
}
}
}

Determine if UIView is visible to the user?

is it possible to determine whether my UIView is visible to the user or not?
My View is added as subview several times into a Tab Bar Controller.
Each instance of this view has a NSTimer that updates the view.
However I don't want to update a view which is not visible to the user.
Is this possible?
Thanks
For anyone else that ends up here:
To determine if a UIView is onscreen somewhere, rather than checking superview != nil, it is better to check if window != nil. In the former case, it is possible that the view has a superview but that the superview is not on screen:
if (view.window != nil) {
// do stuff
}
Of course you should also check if it is hidden or if it has an alpha > 0.
Regarding not wanting your NSTimer running while the view is not visible, you should hide these views manually if possible and have the timer stop when the view is hidden. However, I'm not at all sure of what you're doing.
You can check if:
it is hidden, by checking view.hidden
it is in the view hierarchy, by checking view.superview != nil
you can check the bounds of a view to see if it is on screen
The only other thing I can think of is if your view is buried behind others and can't be seen for that reason. You may have to go through all the views that come after to see if they obscure your view.
This will determine if a view's frame is within the bounds of all of its superviews (up to the root view). One practical use case is determining if a child view is (at least partially) visible within a scrollview.
Swift 5.x:
func isVisible(view: UIView) -> Bool {
func isVisible(view: UIView, inView: UIView?) -> Bool {
guard let inView = inView else { return true }
let viewFrame = inView.convert(view.bounds, from: view)
if viewFrame.intersects(inView.bounds) {
return isVisible(view: view, inView: inView.superview)
}
return false
}
return isVisible(view: view, inView: view.superview)
}
Older swift versions
func isVisible(view: UIView) -> Bool {
func isVisible(view: UIView, inView: UIView?) -> Bool {
guard let inView = inView else { return true }
let viewFrame = inView.convertRect(view.bounds, fromView: view)
if CGRectIntersectsRect(viewFrame, inView.bounds) {
return isVisible(view, inView: inView.superview)
}
return false
}
return isVisible(view, inView: view.superview)
}
Potential improvements:
Respect alpha and hidden.
Respect clipsToBounds, as a view may exceed the bounds of its superview if false.
The solution that worked for me was to first check if the view has a window, then to iterate over superviews and check if:
the view is not hidden.
the view is within its superviews bounds.
Seems to work well so far.
Swift 3.0
public func isVisible(view: UIView) -> Bool {
if view.window == nil {
return false
}
var currentView: UIView = view
while let superview = currentView.superview {
if (superview.bounds).intersects(currentView.frame) == false {
return false;
}
if currentView.isHidden {
return false
}
currentView = superview
}
return true
}
I benchmarked both #Audrey M. and #John Gibb their solutions.
And #Audrey M. his way performed better (times 10).
So I used that one to make it observable.
I made a RxSwift Observable, to get notified when the UIView became visible.
This could be useful if you want to trigger a banner 'view' event
import Foundation
import UIKit
import RxSwift
extension UIView {
var isVisibleToUser: Bool {
if isHidden || alpha == 0 || superview == nil {
return false
}
guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else {
return false
}
let viewFrame = convert(bounds, to: rootViewController.view)
let topSafeArea: CGFloat
let bottomSafeArea: CGFloat
if #available(iOS 11.0, *) {
topSafeArea = rootViewController.view.safeAreaInsets.top
bottomSafeArea = rootViewController.view.safeAreaInsets.bottom
} else {
topSafeArea = rootViewController.topLayoutGuide.length
bottomSafeArea = rootViewController.bottomLayoutGuide.length
}
return viewFrame.minX >= 0 &&
viewFrame.maxX <= rootViewController.view.bounds.width &&
viewFrame.minY >= topSafeArea &&
viewFrame.maxY <= rootViewController.view.bounds.height - bottomSafeArea
}
}
extension Reactive where Base: UIView {
var isVisibleToUser: Observable<Bool> {
// Every second this will check `isVisibleToUser`
return Observable<Int>.interval(.milliseconds(1000),
scheduler: MainScheduler.instance)
.map { [base] _ in
return base.isVisibleToUser
}.distinctUntilChanged()
}
}
Use it as like this:
import RxSwift
import UIKit
import Foundation
private let disposeBag = DisposeBag()
private func _checkBannerVisibility() {
bannerView.rx.isVisibleToUser
.filter { $0 }
.take(1) // Only trigger it once
.subscribe(onNext: { [weak self] _ in
// ... Do something
}).disposed(by: disposeBag)
}
Tested solution.
func isVisible(_ view: UIView) -> Bool {
if view.isHidden || view.superview == nil {
return false
}
if let rootViewController = UIApplication.shared.keyWindow?.rootViewController,
let rootView = rootViewController.view {
let viewFrame = view.convert(view.bounds, to: rootView)
let topSafeArea: CGFloat
let bottomSafeArea: CGFloat
if #available(iOS 11.0, *) {
topSafeArea = rootView.safeAreaInsets.top
bottomSafeArea = rootView.safeAreaInsets.bottom
} else {
topSafeArea = rootViewController.topLayoutGuide.length
bottomSafeArea = rootViewController.bottomLayoutGuide.length
}
return viewFrame.minX >= 0 &&
viewFrame.maxX <= rootView.bounds.width &&
viewFrame.minY >= topSafeArea &&
viewFrame.maxY <= rootView.bounds.height - bottomSafeArea
}
return false
}
I you truly want to know if a view is visible to the user you would have to take into account the following:
Is the view's window not nil and equal to the top most window
Is the view, and all of its superviews alpha >= 0.01 (threshold value also used by UIKit to determine whether it should handle touches) and not hidden
Is the z-index (stacking value) of the view higher than other views in the same hierarchy.
Even if the z-index is lower, it can be visible if other views on top have a transparent background color, alpha 0 or are hidden.
Especially the transparent background color of views in front may pose a problem to check programmatically. The only way to be truly sure is to make a programmatic snapshot of the view to check and diff it within its frame with the snapshot of the entire screen. This won't work however for views that are not distinctive enough (e.g. fully white).
For inspiration see the method isViewVisible in the iOS Calabash-server project
The simplest Swift 5 solution I could come up with that worked in my situation (I was looking for a button embedded in my tableViewFooter).
John Gibbs solution also worked but in my cause I did not need all the recursion.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let viewFrame = scrollView.convert(targetView.bounds, from: targetView)
if viewFrame.intersects(scrollView.bounds) {
// targetView is visible
}
else {
// targetView is not visible
}
}
In viewWillAppear set a value "isVisible" to true, in viewWillDisappear set it to false. Best way to know for a UITabBarController subviews, also works for navigation controllers.
Another useful method is didMoveToWindow()
Example: When you push view controller, views of your previous view controller will call this method. Checking self.window != nil inside of didMoveToWindow() helps to know whether your view is appearing or disappearing from the screen.
This can help you figure out if your UIView is the top-most view. Can be helpful:
let visibleBool = view.superview?.subviews.last?.isEqual(view)
//have to check first whether it's nil (bc it's an optional)
//as well as the true/false
if let visibleBool = visibleBool where visibleBool { value
//can be seen on top
} else {
//maybe can be seen but not the topmost view
}
try this:
func isDisplayedInScreen() -> Bool
{
if (self == nil) {
return false
}
let screenRect = UIScreen.main.bounds
//
let rect = self.convert(self.frame, from: nil)
if (rect.isEmpty || rect.isNull) {
return false
}
// 若view 隐藏
if (self.isHidden) {
return false
}
//
if (self.superview == nil) {
return false
}
//
if (rect.size.equalTo(CGSize.zero)) {
return false
}
//
let intersectionRect = rect.intersection(screenRect)
if (intersectionRect.isEmpty || intersectionRect.isNull) {
return false
}
return true
}
In case you are using hidden property of view then :
view.hidden (objective C) or view.isHidden(swift) is read/write property. So you can easily read or write
For swift 3.0
if(view.isHidden){
print("Hidden")
}else{
print("visible")
}