Setting dynamic height on UItextview to fit content programmatically - ios

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

Related

Measuring the height of lines in UITextView

I am trying to measure the height of lines to place an overlay over specific text in a UITextView. Specifically what I mean by lines is text delimitated with \n. So one line can actually span multiple lines and that is what I am trying to measure. I have tried a few different approaches, however, I can't seem to calculate the height correctly. Another weakness with my current approaches is it has to be all calculated on the main synchronous thread which is less than ideal because I have a lot calculations to do.
EXAMPLE UI APPROACH
measurement.font = UIFont(name: "Menlo", size: 16)
measurement.frame = CGRect(x: 0, y: 0, width: 10000, height: 50)
func heightForString(line: String) -> CGFloat {
var height : CGFloat = 0
measurement.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 10000)
measurement.sizeToFit()
height = measurement.frame.height
print(height, lineHeight)
measurement.frame = CGRect(x: 0, y: 0, width: 10000, height: 50)
return height
}
// Comparing the returned height to measurement.font?.lineHeight and padding shows the results make little sense... :(
EXAMPLE PRINT
Should be 1 line
30.0 18.625
Should be 3 lines
43.6666666666667 18.625
func heightForString(line: String) -> CGFloat {
let textView = UITextView()
let maxwidth = UIScreen.main.bounds.width
textView.frame = CGRect(x:0,y: 0,width: maxwidth,height: CGFloat(MAXFLOAT))
textView.textContainerInset = UIEdgeInsets.zero
textView.textContainer.lineFragmentPadding = 0
textView.font = UIFont(name: "Menlo", size: 16)
textView.text = line
textView.isScrollEnabled = false
textView.sizeToFit()
return textView.frame.size.height
}

UITextField padding

Im having trouble creating padding for leftView property of UITextField. The code I have now creates padding for the text which is nice, but I need it for the imageView.
UITextField Subclass method:
Notice x position of paddingView frame doesn't extend view to the right like expected.
func setLeftView(imageView: UIImageView, withPadding padding: CGFloat) {
let height = imageView.frame.height
let width = imageView.frame.width + padding
let paddingView = UIView(frame: CGRect(x: 36, y: 0, width: width, height: height))
paddingView.addSubview(imageView)
self.leftView = paddingView
self.leftViewMode = .always
}
Result:
You need to make changes in your function, just set frame of imageView and set paddingView's X position to 0
func setLeftView(imageView: UIImageView, withPadding padding: CGFloat) {
let height = imageView.frame.height
let width = imageView.frame.width + padding
// Set x position to 0
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: height))
/// You need to set frame of imageView so put this line
imageView.frame = CGRect(x: padding, y: 0, width: imageView.frame.width, height: height)
paddingView.addSubview(imageView)
self.leftView = paddingView
self.leftViewMode = .always
}
you have to override:
override func leftViewRect(forBounds bounds: CGRect) -> CGRect

How to use UIScrollView to show edge of the next view

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.

UIScrollView with horizontal paging using UIView as Subviews

I have seen answers for this following question. I am new to swift programming and I am trying to implement a similar feature. I just wondered if you had any guidance on how to achieve this in swift 3 Xcode 8. I have been searching around for a suitable solution but I've had no luck.
I am trying use UIViews as a subview of UIscrollviews. I would also like to have each view fill the screen when pressed and shows another UIView. I have seen a similar feature on the GOLF app by 'Tyler the Creator'
The feature I am trying achieve is pictured below.
Any help you could give would be greatly appreciated.
This is a representation of the feature I am trying to create.
let scrollView : UIScrollView = UIScrollView(frame: CGRect(x: 80, y: 80,
width: 250, height: 300))
scrollView.isPagingEnabled = true
scrollView.backgroundColor = .orange
view.addSubview(scrollView)
let numberOfPages :Int = 5
let padding : CGFloat = 15
let viewWidth = scrollView.frame.size.width - 2 * padding
let viewHeight = scrollView.frame.size.height - 2 * padding
var x : CGFloat = 0
for i in 0...numberOfPages{
let view: UIView = UIView(frame: CGRect(x: x + padding, y: padding, width: viewWidth, height: viewHeight))
view.backgroundColor = UIColor.white
scrollView .addSubview(view)
x = view.frame.origin.x + viewWidth + padding
}
scrollView.contentSize = CGSize(width:x+padding, height:scrollView.frame.size.height)
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
var colors:[UIColor] = [.red, .blue, .green, .yellow]
var frame: CGRect = CGRect(x: 0, y: 0, width: 0, height: 0)
override func viewDidLoad() {
super.viewDidLoad()
scrollView.isPagingEnabled = true
scrollView.backgroundColor = .orange
view.addSubview(scrollView)
let numberOfPages :Int = 3
let padding : CGFloat = 15
let viewWidth = scrollView.frame.size.width - 2 * padding
let viewHeight = scrollView.frame.size.height - 2 * padding
var x : CGFloat = 0
for i in 0...numberOfPages{
let view: UIView = UIView(frame: CGRect(x: x + padding, y: padding, width: viewWidth, height: viewHeight))
view.backgroundColor = colors[i]
scrollView .addSubview(view)
x = view.frame.origin.x + viewWidth + padding
}
scrollView.contentSize = CGSize(width:x+padding, height:scrollView.frame.size.height)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You should use UIPageViewController. That class is exactly meant to do what you are looking for with ease.
See UIPageViewController
You can embed your UIPageViewController in a UIContainerView to fit it anywhere.

UIScrollView not paging correct distance

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.

Resources