I'm creating a very simple detail view that has a few pictures the user can page through at the top, with some details on the item name on the bottom. However my scrollview does not page to the correct place, as so:
My scrollview's frame & the frame of its subviews are set as follows:
override func viewDidLoad() {
super.viewDidLoad()
self.pictures.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height / CGFloat(2))
self.pictures.clipsToBounds = false
let width = pictures.frame.width
let height = pictures.frame.height
/*
let view1 = UIView(frame: CGRectMake(pictures.frame.origin.x, 0, scrollViewWidth, scrollViewHeight))
let view2 = UIView(frame: CGRectMake(pictures.frame.origin.x + scrollViewWidth, 0, scrollViewWidth, scrollViewHeight))
let view3 = UIView(frame: CGRectMake(pictures.frame.origin.x + scrollViewWidth * 2, 0, scrollViewWidth, scrollViewHeight))
let view4 = UIView(frame: CGRectMake(pictures.frame.origin.x + scrollViewWidth * 3, 0, scrollViewWidth, scrollViewHeight))
*/
for index in 0...2 {
let frame = CGRectMake(width * CGFloat(index), 0, width, height)
let view = UIView(frame: frame)
views.append(view)
pictures.addSubview(view)
}
views[0].backgroundColor = UIColor.redColor()
views[1].backgroundColor = UIColor.blueColor()
views[2].backgroundColor = UIColor.orangeColor()
pictures.delegate = self
pictures.contentSize = CGSizeMake(width * CGFloat(views.count), height)
}
Does anyone have any suggestions as to how I could fit this? Am I implementing my scrollview incorrectly?
The problem is you trying to access self.view.frame in viewDidLoad() function. In the moment when it called views are not laid out properly yet. Frame of the view will be of size what you specified in storyboard, probably {0, 0, 600, 600}.
Solution: You may put your page-initializing code to viewWillAppear, or you may consider using Auto Layout instead of directly manipulating frames.
You have to add +20 in your width because in scrollView some space is available between two page.
So add 20 it may solve your problem.
Related
A UIScrollView pins its content view by default to its top edge, i.e. when you add some content to a scroll view with a vertical scrolling axis and the content's total height is smaller than the height of the whole scroll view, the content is displayed at the top of the scroll view.
Is there a way to change this behavior and make the scroll view pin its content view to its bottom?
(In other words: I would like the content to "grow from the bottom".)
it is possible to add a view to the scrollview in a certain position
this is simple example code:
class ViewController: UIViewController {
var scrollView: UIScrollView!
var views: UIView!
override func viewDidLoad() {
super.viewDidLoad()
views = UIView.init()
scrollView = UIScrollView.init()
scrollView.addSubview(views)
self.view.addSubview(scrollView)
addViewToBottomScroll()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
views.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 1300)
scrollView.frame = self.view.frame
scrollView.contentSize = views.frame.size
}
func addViewToScrollView() {
let label = UILabel(frame: CGRect(x: 0, y: 10, width: self.view.frame.width, height: 40))
label.text = "ScrollView"
label.textAlignment = .center
views.addSubview(label)
let bottomView = UIView(frame: CGRect(x: 15.0, y: 950.0, width: self.view.frame.width - 30, height: 300))
bottomView.backgroundColor = .yellow
views.addSubview(bottomView)
}
}
hopefully answer your question
The usual workaround is to flip the scroll view, then flip the content view.
e.g.
scrollView.transform = CGAffineTransform(scaleX: 1, y: -1)
contentView.transform = CGAffineTransform(scaleX: 1, y: -1)
I'd really like to know a cleaner solution myself.
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 have been fighting with this all morning and can't seem to find a solution. I have created a UIImageView, filled it with red, then added it to a UIScrollView and set the contentSize to the size of the UIImageView. If I print the contentOffset i see (0, 0) and if I print the contentSize and the UIImageView.frame.size they are the same but the red "image" always appears smaller than what the scrollView thinks the contentSize is.
If I scroll all the way to the top I see a cyan stripe about 100 pixels high above the red image and the scroll bar will not make it all the way to the top of what I believe the top of my scroll view to be. Although the top of the scroll bar does line up with the top of my red window so it would seem as though the scroll view is confused as to where it actually lives. Or more likely, I'm confused
Here is my what seems like very simple code...
imgHorizon = UIImage.init(named:"horizon")!
imgBezel = UIImage.init(named:"bezel_transparent")!
imgWings = UIImage.init(named:"wings_transparent")!
imgViewHorizon = UIImageView.init()
imgViewBezel = UIImageView.init()
imgViewWings = UIImageView.init()
svHorizon = UIScrollView.init()
super.init(coder: aDecoder)
imgViewHorizon = UIImageView(frame: CGRect(x: 0, y: 0, width: imgBezel.size.width, height: imgHorizon.size.height))
imgViewHorizon.backgroundColor = UIColor.red
imgViewBezel = UIImageView(frame: CGRect(x: 0, y: 0, width: imgBezel.size.width, height: imgBezel.size.height))
imgViewBezel.contentMode = UIViewContentMode.center
imgViewBezel.clipsToBounds = true
imgViewBezel.image = imgBezel
imgViewWings = UIImageView(frame: CGRect(x: 0, y: 0, width: imgBezel.size.width, height: imgBezel.size.height))
imgViewWings.contentMode = UIViewContentMode.center
imgViewWings.clipsToBounds = true
imgViewWings.image = imgWings
svHorizon = UIScrollView(frame: CGRect(x: 0, y: 0, width: imgBezel.size.width, height: imgBezel.size.width))
svHorizon.contentSize = CGSize(width: imgBezel.size.width, height: imgHorizon.size.height)
svHorizon.contentMode = UIViewContentMode.scaleToFill
svHorizon.bounces = false
svHorizon.backgroundColor = UIColor.cyan
svHorizon.contentOffset = CGPoint(x: 0, y: 0)
svHorizon.addSubview(imgViewHorizon)
addSubview(svHorizon)
addSubview(imgViewBezel)
addSubview(imgViewWings)
From the discussion in the comments it turns out that the Adjust Scroll View Insets option was checked in the attributes inspector of the ViewController. Unchecking it resolved the problem. Have a look at the image below. You need to uncheck the highlighted option.
I have a horizontal scroll view and I have enabled paging option. So I have added total 3 image views in 3 pages "horizontally". I want my last page to display first. i.e. start from end right. Here is my code:
self.myScrollView.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height)
let scrollViewWidth = self.myScrollView.frame.width
let scrollViewHeight = self.myScrollView.frame.height
let imgOne = UIImageView(frame: CGRectMake(0, 0, scrollViewWidth, scrollViewHeight))
let imgTwo = UIImageView(frame: CGRectMake(-(scrollViewWidth), 0, scrollViewWidth, scrollViewHeight))
let imgThree = UIImageView(frame: CGRectMake(-(scrollViewWidth)*2, 0, scrollViewWidth, scrollViewHeight))
imgOne.image = UIImage(named: "0")
imgTwo.image = UIImage(named: "1")
imgThree.image = UIImage(named: "2")
self.myScrollView.addSubview(imgOne)
self.myScrollView.addSubview(imgTwo)
self.myScrollView.addSubview(imgThree)
self.myScrollView.contentSize = CGSizeMake(scrollViewWidth*3, 0)
Initially set contentOffset to scrollViewWidth*2 which will scroll horizontally to two pages:
scrollView.contentOffset = CGPointMake(scrollViewWidth*2,0)
and you can set to whatever value you want in X value to scroll horizontally.
Just by programmatically coding, I'm trying to achieve something like below image. TextView and ContainerView are embedded in scrollview although it is not shown in the image.
Here is my code for this.
override func viewDidLoad() {
super.viewDidLoad()
let margin = CGFloat(10)
textView = UITextView()
textView.text = "asdadaasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadaadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadadasdadadaddadd"
let contentSize = textView.sizeThatFits(textView.bounds.size)
textView.frame = CGRectMake(margin, 0, self.view.frame.width - 2 * margin, contentSize.height)
let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height - 50))
let containerView = UIView(frame: CGRectMake(margin, contentSize.height + margin, self.view.frame.width - 2 * margin, self.view.frame.height))
let button1 = UIButton(frame: CGRectMake(margin, contentSize.height + containerView.frame.height + 2 * margin, (view.frame.width - 2 * margin)/2, 30))
scrollView.contentSize = CGSize(width: self.view.frame.width, height: button1.frame.origin.y + button1.frame.height + margin)
self.view.addSubview(scrollView)
scrollView.addSubview(textView)
scrollView.addSubview(containerView)
scrollView.backgroundColor = UIColor.blueColor()
containerView.backgroundColor = UIColor.yellowColor()
textView.backgroundColor = UIColor.greenColor()
}
The problem is that textview's height is not dynamic to show all content in textview.
I've confirmed that if I add below code in viewdidappear, it does make the height of textview dynamic but it is stacked under Container view because below code runs after ViewDidLoad configured everything.
let contentSize = textView.sizeThatFits(textView.bounds.size)
textView.frame = CGRectMake(margin, 0, self.view.frame.width - 2 * margin, contentSize.height)
What would be the best approach for this kind of problem?
I think this is caused due to rendering order in viewdidload but I'm not 100% sure. It would be great if someone can talk about rendering order too.
To answer the original question, your call to sizeThatFits has no idea how tall the TextView should be because it has no idea how wide you want it to be. I think before the sizeThatFits line you want the following:
textView.frame.size.width = self.view.frame.width - 2 * margin