I want to add some n views inside a table cell (lets say each view is a row). Based on the designerExpertiseList count, i have created a view for each row and added 1 image view and 1 label.
But when i scroll, the data for cells is not correct. If i long press on a cell, i can see a different view overlapped with the one visible now. Please check the attached screenshots
1st time when the view is loaded : http://i.stack.imgur.com/M8itL.png
After i scroll down, scroll up again and long press: http://i.stack.imgur.com/AuTG0.png
And when i scroll up, the data which was correct the first time for few cell, even that is getting messed up. I even tried to add these dynamic views only once, on First Render.
There are the global declarations:
let rowHeight:CGFloat = 20.0
let imageWidth:CGFloat = 15.0
let imageHeight:CGFloat = 15.0
let labelX:CGFloat = 30.0
let labelHeight:CGFloat = 20.0
var firstRender:[Bool] = [Bool]()
code inside tableView cellForRowAtIndexPath method:
self.designer = AppController.getElementFromDesignersList(indexPath.section)
cell.designerName.text = designer.getFirstName() + " " + designer.getLastName()
cell.location.text = designer.getAddress()
// Add more ROWS of expertise if exist! Getting only 1st expertise now, if it exists
let expertiseList = self.designer.getExpertiseList()
if self.firstRender[indexPath.section] {
var i:Int = 0
for e in expertiseList {
let v = UIView(frame: CGRectMake(0, CGFloat(i)*rowHeight, cell.frame.width, rowHeight))
v.backgroundColor = UIColor.yellowColor()
let im = UIImageView(frame: CGRectMake(0, CGFloat(i)*imageHeight, imageWidth, imageHeight ))
//print("expertise image path: ", e.getImagePath())
im.af_setImageWithURL(
NSURL(string: e.getImagePath())!,
placeholderImage: UIImage(named: "default_galary_demo2")!
)
im.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(im)
// Adding constraints
NSLayoutConstraint(item: im, attribute: .CenterY, relatedBy: .Equal, toItem: v, attribute: .CenterY, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: im, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: v, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 0.0).active = true
im.widthAnchor.constraintEqualToAnchor(nil, constant: imageWidth).active = true
im.heightAnchor.constraintEqualToAnchor(nil, constant: imageHeight).active = true
// cell.frame.width - im.frame.width - 50
let label = UILabel(frame: CGRectMake(0, CGFloat(i)*labelHeight, UIScreen.mainScreen().bounds.width - imageWidth, labelHeight))
label.font = UIFont(name: "OpenSans", size: 12)
print("expertise dump: ", dump(e.getExpertiseValuesList()))
//print("expertise str: ", e.getExpertiseValuesList().map({"\($0.getName())"}).joinWithSeparator(","))
label.text = e.getExpertiseValuesList().map({"\($0.getName())"}).joinWithSeparator(",")
//label.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(label)
NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: v, attribute: .CenterY, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: im, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 10.0).active = true
cell.designerExpertiseView.addSubview(v)
i += 1
}
self.firstRender[indexPath.section] = false
I don't think it correct to render cell only when if self.firstRender[indexPath.section] because when user scroll table view, the cells out of screen will be reused to show others different indexPaths.
Another thing, you create constraints, but not use them
You could use UIStackViews to handle the constraints for you. You would:
Greatly reduce the boilerplate-constraint code in your class;
Simplify adding subviews;
Allow for Adaptive Layouts.
Add a Vertical UIStackViewFor into your UITableViewCell. For more complex layouts, you can either embed more UIStackViews into the Vertical stack view or mix AutoLayout and UIStackViews.
I've quickly tried to recreate your layout and I've added a UIStackView. This implementation uses AutoLayout for some fixed components (like the profile picture) and the stack view to handle UILabels added programatically.
I hope this helps!
As it seems, adding views programmatically to cell using .addSubView() won't go well with reusable cell. It happens since cells are reused when go out of view, but when become visible again, the subviews are being added again.
The solution/workaround I used was to add another placeholder view from storyboard. And i removed the view from superview using .removeFromSuperview() when not needed.
I know this method is not good
Ex: Lets say I put 10 views in a cell ( assuming 10 is maximum views i need), but many of them might not be needed based on the data i get from server. So i will remove most of them from the view.
I am still looking for a better/actual solution for this issue.
Related
I have a UICollectionView which contains five cells. In there, I have a UIImageView, two UILabels and a UITextView. I want to change the height of the textview based on the text it contains, so afterwards I can set the height of the entire cell based on the height of the UITextView and the labels above them. Let me demonstrate with a screenshot.
So, as you can tell, the red background shows the height of the UITextView is not right. I set up the UITextView like this:
let commentTextView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.text = "This is just some text to act as a description"
textView.textContainerInset = UIEdgeInsets(top: 0, left: -4, bottom: 0, right: 0)
textView.font = UIFont.systemFont(ofSize: 16.0)
textView.textColor = UIColor.darkGray
textView.isEditable = false
textView.isSelectable = false
textView.backgroundColor = UIColor.red
textView.frame.size.width = (UIScreen.main.bounds.width - 16 - 16 - 60 - 8)
let numberOfLines = (textView.contentSize.height / textView.font!.lineHeight)
var textViewHeight = (textView.font?.lineHeight)! * floor(numberOfLines)
textView.frame.size.height = textViewHeight
return textView
}()
This does not create the wrong height, I think. I think the problem can be found in my constraints, which has a height constraint (if I delete it, the UITextView disappears). If I change the multiplier (currently set at 0.3), I have different heights, but I want this to be dynamically. So in my opinion, I would need to set a dynamic variable inside the multiplier, but I have no idea how to compose it. Could anyone help? Here are my constraints:
// top constraints
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .top, relatedBy: .equal, toItem: workoutLabel, attribute: .bottom, multiplier: 1, constant: 2)])
// left constraint
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .left, relatedBy: .equal, toItem: profilePictureImageView, attribute: .right, multiplier: 1, constant: 16)])
// right constraint
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .right, relatedBy: .equal, toItem: self.commentTextView.superview, attribute: .right, multiplier: 1, constant: -16)])
// height constraint
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 0.3, constant: 1)])
Cheers guys!
You could consider another way about dynamic height of UITextView. It's known about intrinsic content size. You could implement dynamic height with that tech.
Originally answered here.
Don't change or give any frame to your UITextView. Just give it leading, trailing, top & bottom constraints. Then if your cell is capable of having automatic size then you don't need to calculate anything for your text view.
When using Auto-Layout for dynamically sizing cells, you don't really need to implement sizeThatFits(...). If the constraints are setup correctly, then you only need to disable the scrolling of the UITextView.
From code:
yourTextView.scrollEnabled = false
From IB:
Select your Text View and open Attributes inspector, then
Now if you are facing problems with making your cell to have dynamic size then please look at this answer.
As mentioned yesterday, I had the textview adapt its size based on the answer of #nayem - thank you!
However, I faced another problem with making the cell height dynamic. I have looked into different solutions, got nothing to work, except when I calculate the height myself and set this as the height to be used. This works on all devices in Simulator, just wondering if this is the right approach. Code below, constraints are set on top, left and right.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let dummyText = "Just a long text to see what will happen to the cell. Will it adapt or not? Let's find out!"
let rectWidth = view.frame.width - 32 - 60 - 16
let rect = NSString(string: dummyText).boundingRect(with:CGSize(width: rectWidth, height: 1000), options: NSStringDrawingOptions.usesFontLeading.union(NSStringDrawingOptions.usesLineFragmentOrigin), attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)], context: nil)
let variableHeight = 16 + 20 + 2 + 20 + 2 + 16 + rect.height
return CGSize(width: view.frame.width, height: variableHeight)
}
Is this the best way to do this? Will this cause problems later on?
So I have successfully converted almost all my project to Swift 3 besides from one View Controller where I manually add constraints.
I was getting errors for
view.addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textView)
let views = ["textView": textView]
var constraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|-70-[textView]-8-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-8-[textView]-8-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)
NSLayoutConstraint.activate(constraints)
So I changed it to
textView.translatesAutoresizingMaskIntoConstraints = true
view.addSubview(textView)
textView.center = CGPoint(x: view.bounds.midX, y: view.bounds.midY)
textView.autoresizingMask = [UIViewAutoresizing.flexibleLeftMargin, UIViewAutoresizing.flexibleRightMargin, UIViewAutoresizing.flexibleTopMargin, UIViewAutoresizing.flexibleBottomMargin]
All I am trying to do is add a UITextView to a UIView in a view controller so it takes up the entire height and width and stays vertically and horizontally in the centre. I'm clearly missing something obvious with my updated code and am unsure why my previous code was refusing to run as I worked in Swift 2.2.
Can anyone answer the correct way of adding constraints in Swift 3 ?
UPDATE:
Here's the error from my original code which was converted to Swift 3.0
2016-09-06 22:08:38.751636 APPNAME[570:65129] -[_SwiftValue
nsli_superitem]: unrecognized selector sent to instance 0x17044a0e0
2016-09-06 22:08:38.752963 APPNAME[570:65129] * Terminating app due
to uncaught exception 'NSInvalidArgumentException', reason:
'-[_SwiftValue nsli_superitem]: unrecognized selector sent to instance
0x17044a0e0'
* First throw call stack: (0x18430c1c0 0x182d4455c 0x184313278 0x184310278 0x18420a59c 0x184d42104 0x184d40948 0x184d3f79c
0x184d3f340 0x100221ec0 0x1002289ac 0x100228be8 0x18a0e5b08
0x18a19f4cc 0x18a19f3a4 0x18a19e6ec 0x18a19e138 0x18a19dcec
0x18a19dc50 0x18a0e2c78 0x1875ab40c 0x1875a00e8 0x18759ffa8
0x18751cc64 0x1875440d0 0x18a0d8348 0x1842b97dc 0x1842b740c
0x1842b789c 0x1841e6048 0x185c67198 0x18a150bd0 0x18a14b908
0x100171a04 0x1831c85b8) libc++abi.dylib: terminating with uncaught
exception of type NSException (lldb)
I had this error at the last line of the following code:
scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(scrollView)
contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentView)
let viewDict = [
"contentView": contentView,
"scrollView": scrollView,
]
let vFormat = "V:|[contentView]|"
let constraints = NSLayoutConstraint.constraints(withVisualFormat: vFormat, options: [], metrics: nil, views: viewDict)
When I set the debugger at the last line, it shows the viewDict type as [String: UIView?] (notice the optional here).
The error does NOT occur when I change the declaration as follows:
let viewDict: [String: UIView] = [
Your first code sample has a syntax error in this line:
textView.addConstraints(constraints: [NSLayoutConstraint])
Presumably it's trying to create an empty array, but there's no point in adding an empty array of constraints, so just delete the line.
Note that your first code sample (with the erroneous line removed) doesn't meet your requirement (“takes up the entire height and width and stays vertically and horizontally in the centre”). You're specifying margins, so the text view won't take up the entire height and width, and the vertical margins are asymmetric, so it won't be centred vertically.
The problem in your second code sample is that you're using the wrong mask. For example, .flexibleLeftMargin tells the system to allow the distance between the left edge of view and the left edge of textView to change, but that is the opposite of what you want.
This should do what you want:
textView.frame = view.bounds
textView.autoresizingMask = [ .flexibleWidth, .flexibleWidth ]
If you want margins, set the frame like this:
var frame = view.bounds.insetBy(dx: 8, dy: 0)
frame.origin.y += 70
frame.size.height -= 78
textView.frame = frame
Note that the size of view must already be at least 16 by 78.
If you want to keep using translatesAutoresizingMaskIntoConstraints=false, rather than the accepted answer, I note the following:
1) I get the errors you get when I generate NSLayoutConstraints using the visual formatting approach - code that compiled file in iOS9.3 SDK and ran fine on devices even those running iOS10 - building with iOS10 SDK crashes with your errors
2) If you change this to manually create the NSLayoutConstraints, e.g. similar to this, then it works as expected:
var allConstraints = [NSLayoutConstraint]()
allConstraints.append(NSLayoutConstraint(item: slideView, attribute: .left, relatedBy: .equal, toItem: slideButton, attribute: .left, multiplier: 1.0, constant: 0.0))
allConstraints.append(NSLayoutConstraint(item: slideView, attribute: .right, relatedBy: .equal, toItem: slideButton, attribute: .right, multiplier: 1.0, constant: 0.0))
allConstraints.append(NSLayoutConstraint(item: slideView, attribute: .top, relatedBy: .equal, toItem: slideButton, attribute: .top, multiplier: 1.0, constant: 0.0))
allConstraints.append(NSLayoutConstraint(item: slideView, attribute: .bottom, relatedBy: .equal, toItem: slideButton, attribute: .bottom, multiplier: 1.0, constant: 0.0))
NSLayoutConstraint.activate(allConstraints)
I don't know what the issue with the more elegant-looking visual formatting, but it appears bust at the moment (or there has been a change to the order of view layout code in iOS between 9.3 and 10).
Using UIWebView to render Third party websites. I cant use WKWebView for various reasons.
There is no Xib/View in storyboard associated with my ViewController. All the UI is loaded in the viewDidLoad() method
let navigationBar = UINavigationBar()
navigationBar.setTranslatesAutoresizingMaskIntoConstraints(false)
navigationBar.barStyle = UIBarStyle.Black
//added navigation items
self.view.addSubview(navigationBar)
UXHelper.createHorizontalConstraints(navigationBar, outerView: self.view, margin: 0)
UXHelper.createConstraint(navigationBar, parent: self.view, to: self.view, constraint: NSLayoutAttribute.Top, margin: 0)
UXHelper.createConstraint(navigationBar, parent: self.view, to: nil, constraint: NSLayoutAttribute.Height, margin: AlgoLinkWebViewController.navBarHeight)
self.webView = UIWebView()
self.webView.backgroundColor = UIColor.whiteColor()
self.webView.opaque = false
self.webView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.webView.delegate = self
self.view.addSubview(self.webView)
UXHelper.createHorizontalConstraints(self.webView, outerView: self.view, margin: 0)
UXHelper.createConstraint(self.webView, parent: self.view, to: self.view, constraint: NSLayoutAttribute.Bottom, margin: 0)
self.view.addConstraint(NSLayoutConstraint(item: self.webView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: navigationBar, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0))
With the above code below is the screenshot of the webView rendered
The expected WebView rendering I am looking for is . This is from a different app that I know uses UIWebView but don't have access to code
NOTE: PLEASE ignore the navigation bar differences
The options I tried are
remove Autoresizing = false
Removing this did not remove the zoomed in state
webView.scalesWebPagesToFit = true
setting this did not make a difference
Initializing UIWebView with the parent view frame size
self.webView = UIWebView(frame: self.view.bounds) - no difference
Set the scroll view scale value to 0.5
did not make a difference
Maybe you use zoom mode at your iPhone6/6plus.
If you want to implement changes in the font size you can use Java Script to do so by adding the fallowing in your viewDidLoad method
var fontSize = 104 //Your preferred font size go here
var jsString = "document.getElementsByTagName('body')[0].style.fontSize='\(fontSize)px'"
webView.stringByEvaluatingJavaScriptFromString(jsString)
webView.stringByEvaluatingJavaScriptFromString(jsString)
I hope that helps!
I'm creating a viewController which contain 2 textViews a title and a fullText. At the moment i've created 2 textViews in the interface builder which is placed below each other and then created following code to change the height to equal to the content. However the issue is that it seem to be delayed, which gives a bad user experience. By delay i mean that it takes 0.5 or 1 sec before it resize? here is my code:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setHeightToContent(self.titleText!)
setHeightToContent(self.fullText!)
scrollView?.contentSize = CGSizeMake(self.view.frame.width, self.fullText!.frame.origin.y + self.fullText!.frame.height)
println(self.fullText!.frame.origin.y + self.fullText!.frame.height)
}
func setHeightToContent(theTextView: UITextView) {
let contentSize = theTextView.sizeThatFits(theTextView.bounds.size)
var frame = theTextView.frame
frame.size.height = contentSize.height
theTextView.frame = frame
var aspectRatioTextViewConstraint = NSLayoutConstraint(item: theTextView, attribute: .Height, relatedBy: .Equal, toItem: theTextView, attribute: .Width, multiplier: theTextView.bounds.height/theTextView.bounds.width, constant: 1)
theTextView.addConstraint(aspectRatioTextViewConstraint)
}
Make each of the text views self-sizing in accordance with its own content, and use constraints so that the scroll view's contentSize is configured automatically based on its content.
I'm trying to do a simple layout programmatically and I'm missing something simple, or have something out of place, I think. The following ViewController should center the label in the super view. However, it crashes with this trimmed message: The view hierarchy is not prepared for the constraint ... When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled... View not found in container hierarchy: ... That view's superview: NO SUPERVIEW The other SO questions with this error message are using nibs for the most part, and I'm tring to avoid that, or use Obj-C instead of swift. This question deals with the topic a bit but is a bit old.
Can anyone point me in the right direction?
class ViewController: UIViewController {
let label1 = UILabel() as UILabel
func layoutView(){
label1.text = "Click to see device configuration"
label1.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(label1)
let viewsDictionary = ["label1":label1]
let label1_H:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("H:|-[label1]-|",
options: NSLayoutFormatOptions(0),
metrics: nil,
views: viewsDictionary)
let label1_V:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("V:|-[label1]-|",
options: NSLayoutFormatOptions(0),
metrics: nil, views:
viewsDictionary)
label1.addConstraints(label1_H) // Comment these 2 lines and it runs, but
label1.addConstraints(label1_V) // of course the label is upper left
}
override func viewDidLoad() {
super.viewDidLoad()
layoutView()
}
}
Those constraints are made between the label and its superview. The constraints should be added to that superview, not to the label.
You're almost there. Just replace the following lines...
label1.addConstraints(label1_H) // Comment these 2 lines and it runs, but
label1.addConstraints(label1_V) // of course the label is upper left
... with the following code:
view.addConstraints(label1_H) // Comment these 2 lines and it runs, but
view.addConstraints(label1_V) // of course the label is upper left
However, the constraints H:|-[label1]-|" and V:|-[label1]-|" are equivalent to H:|-8-[label1]-8-|" and V:|-8-[label1]-8-|" (see the iOS Developer Library for more details on default margins). Thus, those constraints are not meant to center your label. In fact, you will just have an enormous label that has 8 unit top, leading, trailing and bottom margins to the viewController's view.
Add the following line of code in your layoutView() method to see what I mean:
label1.backgroundColor = UIColor.orangeColor()
It can be OK but if you really want to center your label, you will have to use the following code:
func layoutView() {
label1.text = "Click to see device configuration"
label1.backgroundColor = UIColor.orangeColor()
//Set number of lines of label1 to more than one if necessary (prevent long text from being truncated)
label1.numberOfLines = 0
label1.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(label1)
let xConstraint = NSLayoutConstraint(item: label1,
attribute: NSLayoutAttribute.CenterX,
relatedBy: NSLayoutRelation.Equal,
toItem: view,
attribute: NSLayoutAttribute.CenterX,
multiplier: 1.0,
constant: 0.0)
self.view.addConstraint(xConstraint)
let yConstraint = NSLayoutConstraint(item: label1,
attribute: NSLayoutAttribute.CenterY,
relatedBy: NSLayoutRelation.Equal,
toItem: view,
attribute: NSLayoutAttribute.CenterY,
multiplier: 1.0,
constant: 0)
self.view.addConstraint(yConstraint)
//Add leading and trailing margins if necessary (prevent long text content in label1 to be larger than screen)
let viewsDictionary = ["label1" : label1]
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-(>=10#750)-[label1]-(>=10#750)-|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary))
}