Tweetbot and Kickstarter for iOS uses a cool feature on user profiles that have a banner image. If you pull down on the tableView the image zooms.
I have it partially working using the following, it changes the height of the image, but strangely, not the width:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
UIImageView *imageView = (UIImageView *)self.tableView.tableHeaderView;
CGFloat y = -scrollView.contentOffset.y;
imageView.frame = CGRectMake(0, scrollView.contentOffset.y, self.cachedImageViewSize.size.width+y, self.cachedImageViewSize.size.height+y);
NSLog(#"%#", NSStringFromCGRect(imageView.frame));
}
Does anyone know how to recreate this effect?
Ok, I figured it out. Here is what I did:
- (void)viewDidLoad {
[super viewDidLoad];
self.imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"church-welcome.png"]];
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
self.cachedImageViewSize = self.imageView.frame;
[self.tableView addSubview:self.imageView];
[self.tableView sendSubviewToBack:self.imageView];
self.tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 170)];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat y = -scrollView.contentOffset.y;
if (y > 0) {
self.imageView.frame = CGRectMake(0, scrollView.contentOffset.y, self.cachedImageViewSize.size.width+y, self.cachedImageViewSize.size.height+y);
self.imageView.center = CGPointMake(self.view.center.x, self.imageView.center.y);
}
}
The above answer for Swift:
Variable Decelerations:
var imageView: UIImageView!
var cachedImageViewSize: CGRect!
viewDidLoad:
var imageView: UIImageView!
var cachedImageViewSize: CGRect!
override func viewDidLoad() {
super.viewDidLoad()
self.imageView = UIImageView(image: UIImage(named: "image-plane"))
imageView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 170)
self.imageView.contentMode = .ScaleAspectFill
self.cachedImageViewSize = self.imageView.frame
self.tableView.addSubview(self.imageView)
self.imageView.center = CGPointMake(self.view.center.x, self.imageView.center.y)
self.tableView.sendSubviewToBack(self.imageView)
self.tableView.tableHeaderView = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, 170))
}
scrollViewDidScroll:
override func scrollViewDidScroll(scrollView: UIScrollView) {
let y: CGFloat = -scrollView.contentOffset.y
if y > 0 {
self.imageView.frame = CGRectMake(0, scrollView.contentOffset.y, self.cachedImageViewSize.size.width + y, self.cachedImageViewSize.size.height + y)
self.imageView.center = CGPointMake(self.view.center.x, self.imageView.center.y)
}
}
Swift 3.0 version of Nic Hubbard's answer:
override func viewDidLoad() {
self.imageView = UIImageView(image: UIImage(named: "header-image"))
imageView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 170)
self.imageView.contentMode = .scaleAspectFill
self.cachedImageViewSize = self.imageView.frame
self.tableView.addSubview(self.imageView)
self.imageView.center = CGPoint(x: self.view.center.x, y:self.imageView.center.y)
self.tableView.sendSubview(toBack: self.imageView)
self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 170))
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
let y: CGFloat = -scrollView.contentOffset.y
if y > 0 {
self.imageView.frame = CGRect(x: 0, y: scrollView.contentOffset.y, width: self.cachedImageViewSize.size.width + y, height: self.cachedImageViewSize.size.height + y)
self.imageView.center = CGPoint(x: self.view.center.x, y: self.imageView.center.y)
}
}
I think it's simply resize the image view to fit the vertial space, while keeping the same aspect ratio.
Swift 2 version of Nic Hubbard's answer:
var imageView: UIImageView!
var cachedImageViewSize: CGRect!
override func viewDidLoad(){
super.viewDidLoad()
self.imageView = UIImageView(image: UIImage(named: "church-welcome.png"))
self.imageView.contentMode = .ScaleAspectFill
self.cachedImageViewSize = self.imageView.frame
self.tableView.addSubview(self.imageView)
self.tableView.sendSubviewToBack(self.imageView)
self.tableView.tableHeaderView = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, 170))
}
func scrollViewDidScroll(scrollView: UIScrollView) {
var y: CGFloat = -scrollView.contentOffset.y
if y > 0 {
self.imageView.frame = CGRectMake(0, scrollView.contentOffset.y, self.cachedImageViewSize.size.width + y, self.cachedImageViewSize.size.height + y)
self.imageView.center = CGPointMake(self.view.center.x, self.imageView.center.y)
}
}
The above mentioned solutions will actually not really provide the exact implementation of how it is in Tweetbot or Tinder [target's profile picture zooming].
I also had to solve this problem and the most perfect implementation I found is to use 2 main elements:
1) Tableview which has content inset
2) Imageview on top of that table view which has an outlet for top constraint and height constraint and which is on top of that empty space provided by the tableview content inset.
So first make sure to set the insets for the tableView and initial height constraint for the imageView. You can do this in viewDidLoad for example.
imageViewHeightConstraint.constant = view.frame.height * 0.3
tableView.contentInset = UIEdgeInsets(top: view.frame.height * 0.3, left: 0, bottom: 0, right: 0)
Then just listen to thew scrollViewDidScroll delegate method and modify the constraints accordingly.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset.y
if -contentOffset >= view.frame.height * 0.3 {
//reset the top constraint
topConstraint.constant = 0
//make the imageview bigger by the additional content offset value
imageViewHeightConstraint.constant = -contentOffset
} else {
topConstraint.constant = -(view.frame.height * 0.3 + contentOffset)
}
}
You may need to inverse the signs according to how you've set your own constraints. And probably there's a way to optimize and not modify the constraints that often, but you get the idea.
Please also note that you need to:
imageView.contentMode = .scaleAspectFill
layer.masksToBounds = true
Otherwise the image will be drawn on top of your first tableView cells [dependent on the image size it may come to cover to whole table view] because it is drawn outside its "bounds".
Related
I have a UIImageView inside a scrollView. Its initial width and height are equal to 100. I want this UIImageView to appear in same size even after zooming. For that I modify its size inside scrollViewDidEndZooming method. This is working fine without a problem. However UiImage of the UiImageView looks blurry when zoom scale is increased. I tried to set contentScale property of the UiImageView, but it is not working.
How I can fix this?
import UIKit
class ViewController: UIViewController {
static var zoomScale: CGFloat = 1.0
let scrollView = UIScrollView()
let contentView = UIView()
let imageView: UIImageView = UIImageView()
let imageViewSize: CGFloat = 100
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .yellow
self.setupScrollView()
self.setuContentView()
self.addImageView()
}
func setupScrollView() {
let vWidth = self.view.frame.width
let vHeight = self.view.frame.height
scrollView.delegate = self
scrollView.frame = CGRect(x: 0, y: 0, width: vWidth, height: vHeight)
scrollView.backgroundColor = .lightGray
scrollView.alwaysBounceVertical = false
scrollView.alwaysBounceHorizontal = false
scrollView.showsVerticalScrollIndicator = true
scrollView.flashScrollIndicators()
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 10.0
self.view.addSubview(scrollView)
}
func setuContentView() {
contentView.frame = scrollView.frame.insetBy(dx: 20, dy: 20)
contentView.backgroundColor = .white
scrollView.addSubview(contentView)
}
func addImageView(){
let image = UIImage(systemName: "arrow.triangle.2.circlepath")
imageView.frame = CGRect(x: 200, y: 200, width: imageViewSize, height: imageViewSize)
imageView.image = image
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .systemBlue
imageView.tintColor = .white
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 0.5 * imageView.bounds.size.width // I want rounded imageView
self.contentView.addSubview(imageView)
}
}
extension ViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.contentView
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
ViewController.zoomScale = scale
imageView.frame = CGRect(x: imageView.frame.origin.x, y: imageView.frame.origin.y, width: imageViewSize/scale, height: imageViewSize/scale)
imageView.layer.cornerRadius = 0.5 * imageView.bounds.size.width
imageView.layer.contentsScale = scale
}
}
In my iOS app, I have a scroll view which has the image view to scroll and zoom images.
What I am unable to do is launch the image view with pre-defined offset and zoom value.
Right now, on launch, the original image is shown which I can zoom-in/out and scroll, but I wanted to zoom-in/out with an offset with default values.
On setting the scrollview.zoomScale, I am not able to scroll the image completely.
Code below:-
let scroller = UIScrollView()
scroller.translatesAutoresizingMaskIntoConstraints = false
scroller.minimumZoomScale = 1
scroller.maximumZoomScale = 5
return scroller
}()
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
The first step:
scrollView = UIScrollView(frame: view.bounds)
view.addSubview(scrollView)
imageView.center = view.center
scrollView.addSubview(imageView)
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 2.0
scrollView.delegate = self
Step 2: implement the UIScrollViewDelegate method
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
Finally:
private func setImagePosition(_ image: UIImage) {
imageView.image = image
let size = imageSize(WithScreen: image)
imageView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
scrollView?.contentSize = size
if let scrollView = scrollView, size.height < scrollView.bounds.size.height {
let offsetY = (scrollView.bounds.size.height - size.height) * 0.5
scrollView.contentInset = UIEdgeInsets(top: offsetY, left: 0, bottom: offsetY, right: 0)
}
}
private func imageSize(WithScreen image: UIImage) -> CGSize {
var size = UIScreen.main.bounds.size
size.height = image.size.height * size.width / image.size.width
return size
}
I am new in ios development, i want to show edge of next view using scrollview initial i got help from this Link, here is my view hierarchy
1) I added scrollview from story board to view controllers's top view
2) I added a view as container view and collection view programmatically as subviews of scrollview.
I displayed the edge of next view but when i go to next page things are not smoothly, i do not know how to handle this massy thing and also i do not know which approach is best for achieving this particular task. here is my code. I'm really stuck and I really don't know what to do.
func addCollectionViewsInsideScrollView(){
scrollView?.delegate = self;
scrollView?.isPagingEnabled=true
scrollView.indicatorStyle = UIScrollViewIndicatorStyle.white
for i in 0...2 {
if i == 0 {
frame = CGRect(x: scrollWidth * CGFloat (i), y: 0, width: scrollWidth - 45,height: scrollHeight)
subView1 = UIView(frame: frame)
subView1.backgroundColor = .white
scrollView?.backgroundColor = .white
scrollView?.addSubview(subView1)
subView1.addSubview(collectionView0)
collectionView0.frame = CGRect(x: subView1.bounds.origin.x, y: 0, width: subView1.bounds.width,height: subView1.bounds.height)
}
if i == 1 {
frame = CGRect(x: scrollWidth * CGFloat (i) - 20, y: 0, width: scrollWidth - 45,height: scrollHeight)
subView2 = UIView(frame: frame)
scrollView?.addSubview(subView2)
subView2.addSubview(collectionView1)
collectionView1.frame = CGRect(x: subView2.bounds.origin.x, y: 0, width: subView2.bounds.width,height: subView2.bounds.height)
}
if i == 2 {
frame = CGRect(x: scrollWidth * CGFloat (i) - 40, y: 0, width: scrollWidth - 45,height: scrollHeight)
subView3 = UIView(frame: frame)
scrollView?.addSubview(subView3)
subView3.addSubview(collectionView2)
collectionView2.frame = CGRect(x: subView3.bounds.origin.x, y: 0, width: subView3.bounds.width,height: subView3.bounds.height)
}
}
scrollView?.contentSize = CGSize(width: (scrollWidth * 3), height: scrollHeight)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
setIndiactorForCurrentPage()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if myPageNo == 1 {
frame = CGRect(x: scrollWidth - 20, y: 0, width: scrollWidth - 40,height: scrollHeight)
let subView = UIView(frame: frame)
self.scrollView?.addSubview(subView)
subView.addSubview(collectionView1)
collectionView1.frame = CGRect(x: subView.bounds.origin.x, y: 0, width: subView.bounds.width,height: subView.bounds.height)
myPageNo -= 0
}
if myPageNo == 2{
frame = CGRect(x: scrollWidth * 2 - 20, y: 0, width: scrollWidth,height: scrollHeight)
let subView = UIView(frame: frame)
self.scrollView.addSubview(subView)
subView.addSubview(collectionView2)
collectionView2.frame = CGRect(x: subView.bounds.origin.x, y: 0, width: subView.bounds.width,height: subView.bounds.height)
myPageNo -= 1
}
}
func setIndiactorForCurrentPage() {
let page = (scrollView?.contentOffset.x)!/scrollWidth
print(scrollView?.contentOffset.x ?? 0)
pageControl?.currentPage = Int(page.rounded())
myPageNo = Int(page.rounded())
if myPageNo == 1 {
setFrame(pageNo: 1)
}
if myPageNo == 2{
setFrame(pageNo: 2)
}
}
func setFrame(pageNo: Int){
if(pageNo == 1){
frame = CGRect(x: scrollWidth + 2, y: 0, width: scrollWidth - 40,height: scrollHeight)
let subView = UIView(frame: frame)
scrollView?.addSubview(subView)
subView.addSubview(collectionView1)
collectionView1.frame = CGRect(x: subView.bounds.origin.x, y: 0, width: subView.bounds.width,height: subView.bounds.height)
}
else if(pageNo == 2){
frame = CGRect(x: scrollWidth * 2, y: 0, width: scrollWidth,height: scrollHeight)
let subView = UIView(frame: frame)
scrollView?.addSubview(subView)
subView.addSubview(collectionView2)
collectionView2.frame = CGRect(x: subView.bounds.origin.x, y: 0, width: subView.bounds.width,height: subView.bounds.height)
}
}
when I back from last page to previous one, all is good but when I go first page to next view i am unable to handle showing edge of next view.
So far I achieved this thing but this is not what i want. I want to control uiscroll using tap gesture and alse want to show last page with left align
`this is my code func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var visibleRect = CGRect()
visibleRect.origin = collectionView.contentOffset
visibleRect.size = collectionView.bounds.size
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath: IndexPath = collectionView.indexPathForItem(at: visiblePoint)
collectionView.scrollToItem(at: visibleIndexPath, at: .left, animated: true)
indexPathArray.removeAll()
}
`
I have been working on several projects where you would have like a peek at the next item you could view. The best solution is probably a UICollectionView as you would not load every UIViewController into view immediately but only when it's almost up. The cell re-use of UICollectionView takes care of that.
Make sure the cell size (which you can calculate depending on the size of your screen) will be something like width - 40px so you just see the edge of the next cell. It's totally possible to have a UIViewController in every cell, in fact you could even do it via Interface Builder nowadays.
UICollectionView already implements UIScrollView so no need to mess with UIScrollView manually. The only thing you need to do is that at the moment somebody stops scrolling you decide which cell you want to scroll to (the next one or stay on the current one) and scroll to that cell animated. For this you need to add a gesture recognizer:
Intercepting pan gestures over a UIScrollView breaks scrolling
Then scroll to the cell most visible when the user stops scrolling:
https://developer.apple.com/documentation/uikit/uicollectionview/1618046-scrolltoitematindexpath
For this you need to know which cell is most visible at that moment. This calculation can be a bit difficult but the gist is that you need to know which cells are in indexPathsForVisibleItems and then see according to their content- or scrollOffset which one is more into view than the other(s). The indexPath of that one should be the indexPath of the one you want to scroll into view.
This solution scales up to millions of items since you're only loading the cells you actually (are about to) see.
If you have many pages I wouldn't do with scroll view but here is my sample code to show next page in scrollview.
class ExampleViewController: UIViewController {
var scrollView = UIScrollView()
var container1 = UIView()
var container2 = UIView()
var container3 = UIView()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.isPagingEnabled = true
scrollView.clipsToBounds = false
view.addSubview(scrollView)
container1.backgroundColor = .red
container2.backgroundColor = .blue
container3.backgroundColor = .yellow
scrollView.addSubview(container1)
scrollView.addSubview(container2)
scrollView.addSubview(container3)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
scrollView.frame = CGRect(x: 0, y: 0, width: view.bounds.width - peekAmount, height: view.bounds.height)
scrollView.contentSize = CGSize(width: 3 * scrollView.frame.width, height: scrollView.frame.size.height)
container1.frame.size = containerSize
container2.frame.size = containerSize
container3.frame.size = containerSize
var xPosition: CGFloat = 0
container1.frame.origin = CGPoint(x: xPosition, y: 0)
xPosition = container1.frame.maxX
container2.frame.origin = CGPoint(x: xPosition, y: 0)
xPosition = container2.frame.maxX
container3.frame.origin = CGPoint(x: xPosition, y: 0)
}
var containerSize: CGSize {
return CGSize(width: scrollView.frame.size.width, height: scrollView.frame.size.height)
}
var peekAmount: CGFloat {
return 80
}
}
There are many different ways to achieve your needs but this is simple enough to give you an idea. I didn't add the page control since you already have the logic.
I think, you use UICollectionView to show edge of the next view.
I am trying to custom a extension for Parallax Header. However, it's not working perfectly. The table header view always floats and overlaps cells.
Extension code:
extension UITableView {
func addImageHeaderView(headerView headerView: UIView, height: CGFloat) {
self.contentInset = UIEdgeInsetsMake(height, 0, 0, 0)
self.contentOffset = CGPoint(x: 0, y: -height)
self.tableHeaderView = headerView
self.tableHeaderView?.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: height)
}
func updateHeaderView(height kTableHeaderHeight: CGFloat) {
var headerRect = CGRect(x: 0, y: -kTableHeaderHeight , width: self.bounds.width, height: kTableHeaderHeight)
if self.contentOffset.y < -kTableHeaderHeight {
headerRect.origin.y = self.contentOffset.y
headerRect.size.height = -self.contentOffset.y
}
self.tableHeaderView?.frame = headerRect
}
}
Implementing Code :
tableView.addImageHeaderView(headerView: viewHeader, height: 100)
func scrollViewDidScroll(scrollView: UIScrollView) {
tableView.updateHeaderView(height: 200)
}
Am I wrong at something? Please show me if you know.
Can you please try to set the following in viewDidLoad
self.edgesForExtendedLayout = UIEdgeInsetsZero;
in swift
self.edgesForExtendedLayout = .None
Also what you can try to do is to check the result of the following
tableView.addImageHeaderView(headerView: viewHeader, height: 0)
func scrollViewDidScroll(scrollView: UIScrollView) {
tableView.updateHeaderView(height: 200)
}
please notice that i changed the height from 100 to 0
i did it because the height value will change the contentInset of your table view and this is exactly the distance from the top corner
I have a scrollView containing a single image. The image's bounds are greater than the scrollView's frame. I have set the scrollView's contentSize to be equal to the image's bounds.
Yet no scrolling. I have no idea what could be causing this.
override func viewDidLoad() {
super.viewDidLoad()
var matches = SortThread.getSortThread().retrieveMatches()
scrollView.frame = CGRectMake(0, navigationController!.navigationBar.frame.height - 20, UIScreen.mainScreen().bounds.width, view.bounds.height - (navigationController!.navigationBar.frame.height - 20))
var imageName = String()
imageName = matches[0].pictures![0]
var parsedImageName = imageName.componentsSeparatedByString(".") as [String]
var newImageName: String = parsedImageName[0] as String
let path = NSBundle.mainBundle().pathForResource("MVLMP Images (Resized)/" + newImageName, ofType: "jpg")
var image = UIImage()
image = UIImage(contentsOfFile: path!)!
var imageView = UIImageView(image: image)
var ratio: CGFloat = image.size.width/image.size.height
var screenHeight: CGFloat = UIScreen.mainScreen().bounds.height - navigationController!.navigationBar.frame.height - 20
if(ratio > 1){
imageView.frame = CGRectMake(0, 0, ((scrollView.bounds.width)/7) * 6, 0)
imageView.frame = CGRectMake(0, 0, imageView.frame.width, imageView.frame.width * ratio)
} else {
imageView.frame = CGRectMake(0, 0, 0, (screenHeight/9)*8)
imageView.frame = CGRectMake(0, 0, imageView.frame.height * ratio, imageView.frame.height)
}
scrollView.addSubview(imageView)
scrollView.contentSize.width = imageView.frame.width
scrollView.contentSize.height = imageView.frame.height
}
}
I've printed out the scrollView.contentSize.width and the scrollView.bounds.width and the content width is in fact greater than the bounds width by about 40.
As Muc Dong commented, I was adding the width of the images to the scrollView's contentSize.width. This was not necessarily the correct thing to do as in my case there is some margin between the images that I was not accounting for. This margin should of been included in the additions to to scrollView.contentSize.width.