I am trying to make my uitextview's corner radius dynamic depending on the number of lines displayed. The text is retrieved from my backend, so the number of lines will vary...
I've tried setting the corner radius in viewDidLoad after the text view is created by using its frame but for some reason that doesn't work. I'm assuming its returning 0 for some reason.
Any help is greatly appreciated.
(I'm cutting out a lot of code just to keep it simple here. Everything is added to the subview properly. Everything is added programmatically - not using the storyboard at all here. The text views display as expected besides the corner radius)
inside viewDidLoad:
questionOneTextBox.layer.cornerRadius = self.questionOneTextBox.frame.height * 0.5
questionTwoTextBox.layer.cornerRadius = self.questionTwoTextBox.frame.height * 0.5
If you are creating the text view programmatically with constraints, then you need to call self.questionOneTextBox.layoutIfNeeded() and self.questionTwoTextBox.layoutIfNeeded(). This will initialise the bounds.
class ViewController: UIViewController {
var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the Text View
self.textView = UITextView()
self.textView.text = "This text will appear in the text view."
self.textView.backgroundColor = .lightGray
self.view.addSubview(self.textView)
// Set the constraints
self.textView.translatesAutoresizingMaskIntoConstraints = false
self.textView.widthAnchor.constraint(equalToConstant: 280).isActive = true
self.textView.heightAnchor.constraint(equalToConstant: 60).isActive = true
self.textView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 60).isActive = true
self.textView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
// This will initialise the bounds
self.textView.layoutIfNeeded()
// Now the this should work
self.textView.layer.cornerRadius = self.textView.frame.height * 0.5
}
}
This what this code looks like.
Views are not laid out (sized) when viewDidLoad is called. Move your corner radius sizing to viewDidLayoutSubviews or viewWillAppear, at which time the sizing has occurred.
Did you set clipsToBounds to true? This might be causing your problem. I hope this helped
Related
Posting a question for the first time here.
So I have been trying to make an animation of an UIimageView. I did that so far. So the image moves from the middle of the screen to the top. I want to be able to make that animation with constraints. But while trying to add some constraints, I receive this error "Unable to activate constraint with anchors error".
here is the code which I try to add some constraints to banditLogo imageview.
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(banditLogo)
view.translatesAutoresizingMaskIntoConstraints = false // autolayout activation
chooseLabel.alpha = 0
signInButtonOutlet.alpha = 0
self.banditLogo.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 304).isActive = true
self.banditLogo.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 94).isActive = true
self.banditLogo.widthAnchor.constraint(equalToConstant: 224).isActive = true
self.banditLogo.heightAnchor.constraint(equalToConstant: 289).isActive = true
}
and here is the func that makes the animation.
this func is being called in viewDidAppear and animatedImage variable of the function is referred to banditLogo UIimageView.
so when the view screen loads up, the image moves to top of the view.
func logoAnimate(animatedImage: UIImageView!, animatedLabel: UILabel!) {
UIView.animate(withDuration: 1.5, delay: 1, options: [.allowAnimatedContent]) {
animatedImage.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 5).isActive = true
animatedImage.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor, constant: 94).isActive = true
} completion: { (true) in
UIView.animate(withDuration: 0.25) {
animatedLabel.alpha = 1
}
}
}
You may find it easier to create a class-level property to hold the image view's top constraint, then change that constraint's .constant value when you want to move it.
Here's a quick example - tapping anywhere on the view will animate the image view up or down:
class AnimLogoViewController: UIViewController {
let banditLogo = UIImageView()
// we'll change this constraint's .constant to change the image view's position
var logoTopConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
if let img = UIImage(systemName: "person.fill") {
banditLogo.image = img
}
view.addSubview(banditLogo)
// I assume this was a typo... you want to set it on the image view, not the controller's view
//view.translatesAutoresizingMaskIntoConstraints = false // autolayout activation
banditLogo.translatesAutoresizingMaskIntoConstraints = false // autolayout activation
// create the image view's top constraint
logoTopConstraint = banditLogo.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 304)
// activate it
logoTopConstraint.isActive = true
// non-changing constraints
self.banditLogo.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 94).isActive = true
self.banditLogo.widthAnchor.constraint(equalToConstant: 224).isActive = true
self.banditLogo.heightAnchor.constraint(equalToConstant: 289).isActive = true
// animate the logo when you tap the view
let t = UITapGestureRecognizer(target: self, action: #selector(self.didTap(_:)))
view.addGestureRecognizer(t)
}
#objc func didTap(_ g: UITapGestureRecognizer) -> Void {
// if the logo image view is at the top, animate it down
// else, animate it up
if logoTopConstraint.constant == 5.0 {
logoTopConstraint.constant = 304.0
} else {
logoTopConstraint.constant = 5.0
}
UIView.animate(withDuration: 1.5, animations: {
self.view.layoutIfNeeded()
})
}
}
I animate views that have constraints by changing constraints, not setting them. Leave the constraints that are static "as is" - that is, use isActive = true. But those you wish to change? Put them in two arrays and activate/deactivte them. Complete the animation like you are by using UIView.animate.
For instance, let's say you wish to move banditLogo from top 304 to top 5, which appears to me to be what you trying to do. Leave all other constraints as is - left (which your code doesn't seem to change), height, and width. Now, create two arrays:
var start = [NSLayoutConstraint]()
var finish = [NSLayoutConstraint]()
Add in the constraints that change. Note that I'm not setting them as active:
start.append(banditLogo.topAnchor.constraint(equalTo: safeAreaView.topAnchor, constant: 305))
finish.append(banditLogo.topAnchor.constraint(equalTo: safeAreaView.topAnchor, constant: 5))
Initialize things in viewDidLoad or any other view controller method as needed:
NSLayoutConstraint.activate(start)
Finally, when you wish to do the animation, deactivate/activate and tell the view to show the animation:
NSLayoutConstraint.deactivate(start)
NSLayoutConstraint.activate(finish)
UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() }
Last piece of critique, made with no intent of being offending.
Something in your code posted feels messy to me. Creating a function to move a single view should directly address the view IMHO, not pass the view into it. Maybe you are trying to move several views this way - in which case this is good code - but nothing in your question suggests it. It's okay to do the animation in a function - that way you can call it when needed. I do this all the time for something like this - sliding a tool overlay in and out. But if you are doing this to a single view, just address it directly. The code is more readable to other coders.
Also, my preference for the start is in viewDidLoad unless the VC is part of a navigation stack. But in that case, don't just use viewDidAppear, set things back to start in viewDidDisappear.
EDIT: looking at the comments, I assumed that yes you have already used translatesAutoresizingMaskIntoConstraints = false properly on every view needed.
I'm trying to set a custom UILabel my navigationItem's titleView. In viewDidLoad I have the following code:
override func viewDidLoad() {
super.viewDidLoad()
let subtopicLabel = UILabel()
subtopicLabel.translatesAutoresizingMaskIntoConstraints = false
subtopicLabel.text = subtopicName
subtopicLabel.font = UIFont.systemFont(ofSize: 20.0)
subtopicLabel.textColor = ThemeService.text120
let width: CGFloat = view.frame.width * 0.7
subtopicLabel.widthAnchor.constraint(equalToConstant: width).isActive = true
subtopicLabel.heightAnchor.constraint(equalToConstant: 40).isActive = true
navigationItem.titleView = subtopicLabel
}
This works fine for iOS 11+ however if I push to this view controller in iOS 10 the label seems to start further up the screen and then jumps down into position. Please see the gif below:
Any ideas what might be causing this and how I can fix it? Tried moving the code to viewDidLayoutSubviews and viewDidAppear, no luck though. Any pointers on this would be greatly appreciated! Thanks!
I am trying to make a photo viewer similar to the Photo's app where the user can zoom in and out on a particular image.
The image is initially sized so fit on the screen but when zoomed can expand to cover the entire screen.
I have this happening in a collectionView cell which is the size of the screen and has paging enabled. In that cell is a scrollView with storyboard constraints set to top/bottom/leading/trailing to it's superview. The rest happens in the code below which is the custom cell.
According to the new behavior of the scrollView introduced in iOS 11, the contentView (imageView in my case) should be centered in the scrollView using:
imageView.centerXAnchor.constraint(equalTo: scrollView.contentLayoutGuide.centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: scrollView.contentLayoutGuide.centerYAnchor).isActive = true
However I don't see it explained anywhere and adding those two lines to my code do absolutely nothing.
The imageView inside the scrollView continues to be positioned in the top left corner.
Hopefully somebody has figured out how to do this and can help.
import UIKit
class ImageCollectionViewCell: UICollectionViewCell, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView! {
didSet {
scrollView.delegate = self
scrollView.minimumZoomScale = 0.2
scrollView.maximumZoomScale = 2.0
scrollView.contentSize = imageView.frame.size
scrollView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: 350).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 350).isActive = true
imageView.centerXAnchor.constraint(equalTo: scrollView.contentLayoutGuide.centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: scrollView.contentLayoutGuide.centerYAnchor).isActive = true
}
}
var imageView = UIImageView()
var image: UIImage? {
get {
return imageView.image
}
set {
imageView.image = newValue
scrollView?.contentSize = imageView.frame.size
}
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.imageView
}
}
I have been facing the same problem for about a day, and I finally figured it out. It is not sufficient to just add the width and height constraints to the image. For this to work using contentLayoutGuide, the image view actually has to be the same size as the scroll view. Therefore, you will have to do either of the following:
Option 1
Change the width and height constraints of the image view to be the same size as the scroll view.
imageView.widthAnchor.constraint(equalToConstant: scrollView.bounds.width).isActive = true
imageView.heightAnchor.constraint(equalToConstant: scrollView.bounds.height).isActive = true
Option 2 - Preferred
Remove the width and height constraints for the image view. Instead, add constraints pinning the top, bottom, leading, and trailing anchors of the image view to the scroll view. I tried both, and this method seemed to work the best.
imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
Depending on how/where you are using this, you may need to use scrollView.layoutMarginGuide for the anchor.
I would also recommend moving this code from didSet to a private method named setup(), or something along those lines. Then, from viewWillAppear, call setup(). This is a more consistent way of doing it. Hope this helped!
I have a UIView in which I have a circular border around it. Here is the code for it:
let v = UIView()
self.view.addSubview(v)
v.backgroundColor = .orange
v.translatesAutoresizingMaskIntoConstraints = false
v.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
v.rightAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
v.topAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
self.view.layoutIfNeeded()
v.layer.cornerRadius = v.frame.width / 2
If I set the ViewController view like this: self.view = mainView (mainView is a subclass of MainView which contains some other subviews), then the result of the corner radius is no longer a circle: Resulting "circle".
However, if I use self.view.addSubview(mainView) (and add autolayout constraints to mainView) and replace self.view.addSubview(v) with self.mainView.addSubview(v) then the circle turns out to be fine.
Why does the circle turn out weird only when I set self.view = mainView, but is fine when I do self.view.addSubview(mainView)?
First where are you replacing the UIViewControllers view with mainView? You should be overriding the loadView method of the UIViewController not doing it in the viewDidLoad method.
Second if you are changing the UIViewControllers view then it will not have it's frame setup when you get to viewDidLoad like the original will and so the UIView v is getting the incorrect layout size which it is then using to determine its corner radius.
Third you shouldn't use layoutIfNeeded in the viewDidLoad as this is way to early to be trying to determine the final layout of the main view (everything is still loading). What you should be doing is overriding viewDidLLayoutSubviews method and setting the corner radius there like this:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
v.layer.cornerRadius = v.frame.width / 2
}
What that also does is if the size of the UIView v changes (orientation change, etc) then it will remain a circle at it's new size otherwise it will just have rounded corners.
Of course you have to make UIView v a class instance variable in order to be able to access it here.
So, I have looked through almost all of Stackoverflow's answers to this particular question and even looked through tutorials that supposedly teach you how to use a scroll view but It doesn't seem to apply for my project..
Here is what I know so far, in order for a Scroll View to properly work you first need to give it a content size. This determines the scrollable height etc.
I have provided some code to give you all a better idea of how I am adding said items into my scrollview. If there is something that I am doing wrong or if there is a better way to go about doing this please let me know, I am still fairly new to Swift and iOS development and in my mind it feels like I am doing it correctly.
The steps I am taking
Create items that I want to display (Input fields, Imageviews etc..)
Add said items to the view of the viewcontroller. (view.addsubview(etc..))
Create a scrollView and set its constraints to be same as the screen / view
Add our view with all the items in it into said scroll view
Relax and everything should work out perfect?????
Here is my code, I know it might be lengthy but I think it might be needed so that the scope of my question is understood
class JobRegistrationController: UIViewController {
// ... Omitted for clarity
lazy var scrollView: UIScrollView = {
let view = UIScrollView(frame: UIScreen.main.bounds)
view.backgroundColor = .red
view.contentSize = CGSize(width: self.view.bounds.width, height: self.view.bounds.height * 2)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
//... Omitted for clarity
let scrollContentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Need so that view controller is not behind nav controller
self.edgesForExtendedLayout = []
view.addSubview(scrollView)
scrollView.addSubview(scrollContentView)
scrollContentView.addSubview(jobTypeField)
scrollContentView.addSubview(jobTypeDividerLine)
// x, y, width and height constraints
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
// x, y, width and height constraints
scrollContentView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollContentView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollContentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollContentView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
// x, y, width and height constraints
jobTypeField.leftAnchor.constraint(equalTo: scrollContentView.leftAnchor, constant: 20).isActive = true
jobTypeField.topAnchor.constraint(equalTo: scrollContentView.topAnchor).isActive = true
jobTypeField.rightAnchor.constraint(equalTo: scrollContentView.rightAnchor, constant: -8).isActive = true
jobTypeField.heightAnchor.constraint(equalToConstant: 30).isActive = true
// x, y, width and height constraints
jobTypeDividerLine.leftAnchor.constraint(equalTo: scrollContentView.leftAnchor, constant: 20).isActive = true
jobTypeDividerLine.topAnchor.constraint(equalTo: jobTypeField.bottomAnchor).isActive = true
jobTypeDividerLine.rightAnchor.constraint(equalTo: scrollContentView.rightAnchor).isActive = true
jobTypeDividerLine.heightAnchor.constraint(equalToConstant: 0.5).isActive = true
Use this method in your class
override func viewDidLayoutSubviews()
{
scrollView.delegate = self
scrollView.contentSize = CGSize(width:self.view.frame.size.width, height: 1000) // set height according you
}
view.contentSize = CGSize(width: self.view.bounds.width, height: self.view.bounds.height * 2)
You should try to log the contentSize in your console after trying to access it. I am not sure if you are setting the correct contentSize here if the self.view.bounds has been calculated correctly when this gets called at that moment. Since it takes time for self.view frame and bounds to be calculated.
Try setting your contentSize after you have added the actual content to it based on the actual total content size.
EDIT:
Add a single UIView inside the scrollView, with the constraints set to top-bottom-leading-trailing, and add your subviews to it. Also, set the same constraints on the scrollView to the superView top-bottom-leading-trailing.
I believe the line of code below is the problem
scrollContentView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
You are setting your content view to the top of the view, when you should be setting it to the top of the scrollview.
I've just overcome a similar issue were I was setting the topAnchor of my first view to the safeAreaLayoutGuide.topAnchorof the scrollView. Everything laid out correctly but the constraint wouldn't show and therefore the entire content of the scrollView didn't move.
The problem is that you don't tell where the bottom of your content is. In other words you need some bottom constraints.
If you use...
scrollContentView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
...you need also to add a constraint to bind at least one view to the bottom of your UIScrollView like:
scrollContentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
... and also bind the last view in the scrollContentView to its bottomAnchor.
jobTypeDividerLine.bottomAnchor.constraint(equalTo: scrollContentView.bottomAnchor).isActive = true
This will sure fix your issue. Because this way the whole constraint sequence is linked from top to bottom.
Bottom line, the UIScrollView is not that smart that it determines its own bottom in every possible way. It is a kind of lazy. If you don't tell him enough it wouldn't simply scroll, while it is clear that your content disappears behind the bottom of your UIScrollView container.