I want to increase the size of collectionView on Scroll. It's called Parallax affect.
I have 3 classes with named
1)class PhotoGalleryViewController' with storyboard.
PhotoGalleryViewController contains collectionView and a cell with imageview.
2) class ListingDetailContentViewController' without storyboard
3) class ListingDetailViewController' with storyboard.
ListingDetailViewController is appending ListingDetailContentViewController as child.
contentViewController = ListingDetailContentViewController(hideComparable: comparableIsHidden)
contentViewController.view.translatesAutoresizingMaskIntoConstraints = false
contentViewController.listingDetailViewController = self
addChild(contentViewController)
view.addSubview(contentViewController.view)
view.sendSubviewToBack(contentViewController.view)
ListingDetailContentViewController is appending photoGalleryViewController using Library AloeStackViewController. AloeStackViewController is UIScrollView so thats why i made its delegate in ListingDetailContentViewController
class ListingDetailContentViewController: AloeStackViewController, PhotoGalleryViewControllerDelegate, UIScrollViewDelegate {
// MARK: - Private functions
private func setupView(ad: Ad) {
// Display photos
if let photos = ad.photos {
photoGalleryViewController = UIStoryboard(name: "Listing", bundle: nil).instantiateViewController(withIdentifier: "PhotoGalleryViewController") as? PhotoGalleryViewController
let photoView = photoGalleryViewController.view!
stackView.addRow(photoView)
stackView.setInset(forRow: photoView, inset: .zero)
photoGalleryViewController.photos = photos
photoGalleryViewController.ad = ad
photoGalleryViewController.delegate = self
actualFrame = photoGalleryViewController.collectionView.frame.height
}
// Some other stuff
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// The link i applied here is https://stackoverflow.com/questions/33481928/imageview-scaling-when-scrolling-down
let offsetY = scrollView.contentOffset.y
if offsetY < 0 {
photoGalleryViewController.collectionView.frame.size.height += actualFrame - offsetY
photoGalleryViewController.view.frame.size.height += actualFrame - offsetY
self.loadViewIfNeeded()
} else {
photoGalleryViewController.collectionView.frame.size.height = actualFrame
photoGalleryViewController.view.frame.size.height = actualFrame
self.loadViewIfNeeded()
}
}
}
I tried this help but not worked for me.
My image is at top of screen. and other things are below.
When ever user scrolls down. Image should be increase.
Kindly help me out to solve this problem.
Update:
Answer:
I have done it with help of library called ParallaxHeader. I simply add this library using Pod and follow the tutorial given in library.
After a lot of struggle I achieved my goal that were asked in Question.
I have done it with help of library called ParallaxHeader.
I simply add this library using Pod and follow the tutorial given in library.
I am attempting to scale dot of the current page to be larger than the dots which are not 'selected'.
I am using the scrollview delegate to ascertain which page is current.
At the moment there is no change to the size of the dot.
How would I go about achieving this?
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var i = 0
if(scrollView.contentOffset.x < scrollView.frame.width){
pageControl.currentPage = 0
pageControl.subviews.forEach {
if(i == 0){
print("Edit")
$0.transform.scaledBy(x: 5, y: 5)
}
pageControl.layoutIfNeeded()
i += 1
}
}else{
pageControl.currentPage = 1
}
}
UIPageControl is not that customizable. Use a library like this one
TAPageControl
Is works the same way as UIPageControl and you can easily customize current and non current page dots by setting the properties dotImage and currentDotImage.
Does anyone know how can I reproduce a similar effect from the native iOS7 weather app?
Basically, the status bar inherits the view's background underneath, but the content doesn't show up.
Also, a 1 pixel line is drawn after the 20 pixels height of the status bar, only if some content is underlayed.
The best thing is to make it through the clipSubview of the view. You put your content into the view and make constraints to left/right/bottom and height. Height on scroll view you check is the cell has minus position and at that time you start to change the height of content (clip) view to get desired effect.
This is a real app you can download and take a look from www.fancyinteractive.com. This functionality will be available soon as next update.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSArray *visibleCells = [convertorsTableView visibleCells];
if (visibleCells.count) {
for (CVConverterTableViewCell *cell in visibleCells) {
CGFloat positionYInView = [convertorsTableView convertRect:cell.frame toView:self.view].origin.y;
[self clipLayoutConstraint:cell.clipHeightLayoutConstraint withPosition:positionYInView defaultHeight:cell.frameHeight];
[cell.converterLabel layoutIfNeeded];
[cell.iconImageView layoutIfNeeded];
}
}
[self checkStatusBarSeperator:scrollView.contentOffset.y];
}
- (void)clipLayoutConstraint:(NSLayoutConstraint *)constraint withPosition:(CGFloat)position defaultHeight:(CGFloat)defaultHeight {
if (position < 0) {
constraint.constant = (defaultHeight - -position - 20 > 10) ? defaultHeight - -position - 20 : 10;
} else
constraint.constant = defaultHeight;
}
You can accomplish this by setting a mask to the table view's layer. You will not be able however to render the animations inside the cells, but you can do those yourself behind the table view, and track their movement with the table view's scrollview delegate methods.
Here is some informations on CALayer masks:
http://evandavis.me/blog/2013/2/13/getting-creative-with-calayer-masks
Swift 5:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let visibleCells = tableView.visibleCells as? [TableViewCell] else { return }
let defaultClipHeight: CGFloat = 24
let statusBarHeight: CGFloat = UIApplication.statusBarHeight
if !visibleCells.isEmpty {
for cell in visibleCells {
let topSpace = cell.frame.size.height - defaultClipHeight - cell.clipBottomConstraint.constant
let cellOffsetY = tableView.contentOffset.y - cell.frame.origin.y + statusBarHeight
if cellOffsetY > topSpace {
let clipOffsetY = cellOffsetY - topSpace
let clipHeight = defaultClipHeight - clipOffsetY
cell.clipHeightConstraint.constant = max(clipHeight, 0)
} else {
cell.clipHeightConstraint.constant = defaultClipHeight
}
}
}
}
Starting Page:
Scrolling First Item:
Scrolling Second Item:
On iOS devices the section headers in UITableView's have a nice behavior where they stick or 'float' to the top of the screen as you scroll through a section. The section headers in my particular case are loaded from other XIB files.
Is it possible to change the section headers depending on whether or not they are currently floating? Specifically I'd like to add a small shadow to appear under the header only while it's stuck to the top of the view.
Thanks!
Here's the function I created to update whether each header has a shadow or not. All the section headers in this case are a UIView subclass ListHeader. They're retained and returned by the viewForHeaderInSection function.
- (void) updateHeaderShadows {
int i=0;
int sectionHeight = 0;
int totalHeight = 0;
UIView * sectionHeader;
while (i<[self numberOfSectionsInTableView:self.tableView]) {
sectionHeight = [self.tableView rectForSection:i].size.height;
sectionHeader = [self tableView:self.tableView viewForHeaderInSection:i];
if ([sectionHeader respondsToSelector:#selector(shadow)]) {
if (sectionHeader.frame.origin.y == totalHeight || sectionHeader.frame.origin.y == totalHeight + sectionHeight - sectionHeader.frame.size.height) {
[((ListHeader *) sectionHeader).shadow setHidden:YES];
} else {
[((ListHeader *) sectionHeader).shadow setHidden:NO];
}
}
totalHeight += sectionHeight;
i++;
}
}
I haven't tested it yet, but I don't see any reason why it wouldn't be possible.
Just make sure you set the right bounds (because your shadow needs to be on top of your view, not above it).
You can use the following approach:
Use scrollView:didScroll: to get notified about scroll-events.
In this method, check whether you need to add your shadow-view to your (floating) header-view.
If so, add it. (just [view addSubview:shadowView].) Something like CGRectMake(0.f, yourDefaultHeaderHeight, 320.f, yourShadowHeight) should be the frame of your shadowView.
Now, update the bounds of view, so it can show your shadowView: CGRectMake(0.f, 0.f - yourShadowHeight, 320.f, yourDefaultHeaderHeight + 2 * yourShadowHeight).
When you find out that your header isn't floating anymore (by using scrollView:didScroll:), remove the shadow-view.
Your headerViews bounds should be 0.f - yourShadowHeight because if you use just 0.f, it'll blur (I don't know why ...).
You would have to have your own UIView in the header. Then you would need a reference to it. Then hook into scrollViewWillBeginDragging: with your UIScrollViewDelegate. In that function, add the shadow to the custom view.
Hook into scrollViewDidEndDragging:willDecelerate: and remove the shadow in this function.
#Anthony Mattox answer for Swift
protocol SectionHeaderWithShadowProtocol where Self: UIView {
var shadow: Bool { get set }
}
class SectionHeaderView: UITableViewHeaderFooterView, SectionHeaderWithShadowProtocol {
#IBOutlet weak var shadowView: UIView!
var shadow: Bool = false {
didSet {
shadowView.isHidden = shadow
}
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
updateHeaderShadows()
}
func updateHeaderShadows() {
var i = 0
var sectionHeight: CGFloat = 0
var totalHeight: CGFloat = 0
while i < numberOfSections() {
sectionHeight = tableView.rect(forSection: i).size.height
if let sectionHeader = tableView.headerView(forSection: i) as? SectionHeaderWithShadowProtocol {
if sectionHeader.frame.origin.y == totalHeight || sectionHeader.frame.origin.y == totalHeight + sectionHeight - sectionHeader.frame.size.height {
sectionHeader.shadow = false
} else {
sectionHeader.shadow = true
}
}
totalHeight += sectionHeight
i += 1
}
}
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")
}