Vertical center on UITableViewCell accessoryView's element - ios

How can I vertically center the content that I have inside a accessoryView from a UITableViewCell? The content is represented by a dynamically added UISwitch that is resized to 50% both width and height using:
switchView.transform = CGAffineTransformMakeScale(0.5, 0.5);
A picture with the problem is:

You cannot change the position of a accessoryView. Please refer to the following question:
Can a standard accessory view be in a different position within a UITableViewCell?
It is okay for you to apply transformation such as scale, and rotation to the accessory view. However, you are not able to apply translate transformation to the accessory view. For example,
switchView.transform = CGAffineTransformTranslate(CGAffineTransformMakeScale(0.5, 0.5),10,10);
Only the scale part of the transformation is applied to the switchView. Looking through the apple documentation, I also found this following line of note:
... The accessory view appears in the right side of the cell.
I don't think apple do want you to customized the location of the accessory view. Based on your question, I also tried changing the centre and the frame of the accessoryView, I do notice that the position of the accessoryView is not moving at all.
In short, If you really want to change the size of the UISwitch and move it to the vertical align it in the table view cell, I think you have only one option: You will have to add UISwitch as a subview of the cell and then you will have to freedom to do whatever you want.

Set the appropriate constraint in interface builder: "Vertical Center in Container".
I would not recommend reducing the size of standard UI elements.

The best way I could do this is by subclassing UITableViewCell, and overriding layoutSubviews and positioning the accessoryView there:
- (void)layoutSubviews {
[super layoutSubviews];
self.accessoryView.frame = CGRectMake(self.accessoryView.frame.origin.x + (self.accessoryView.frame.size.width / 2), (self.frame.size.height - (self.accessoryView.frame.size.height / 2)) / 2, self.accessoryView.frame.size.width, f.size.height);
}
I would probably advise you not to resize Apple's prebuilt UI components, especially because Apple's Human Interface Guidelines suggest:
Give tappable controls a hit target of about 44 x 44 points.
The resized switches are smaller.
Nonetheless, I hope this helps!

iOS 15.5 / Swift 13.4.1
It's a little bit weird when I set a UIImageView to cell.accessoryView
let iconView = UIImageView(image: .sfSymbol(of: "lock.fill", size: 17, weight: .semibold))
iconView.bounds = .init(x: 0, y: 0, width: 32, height: 32)
iconView.contentMode = .scaleAspectFit
iconView.debugBorder() // Show 1pt border
self.accessoryView = iconView
But when I use an UIView as image view's container, add top/bottom/leading/trailing auto layout, then set container to cell.accessoryView, the image is vertically centered in cell:
let imageContainerView = UIView(frame: .init(x: 0, y: 0, width: 32, height: 32))
let imageView = UIImageView(image: .sfSymbol(of: "lock.fill", size: 17, weight: .semibold))
imageView.contentMode = .scaleAspectFit
imageContainerView.addSubview(imageView)
imageView.debugBorder() // Show 1pt border
imageView.labr.box(imageContainerView, padding: 0).done() // Auto Layout: fill container
self.accessoryView = imageContainerView

Related

How to add a decorator at left of a single paragraph in a UITextView?

I have a custom UITextView, and I have several styles for it.
One of them includes adding a vertical line all to the left besides the actual text for an specific paragraph. This line should have the same size as the paragraph and scroll with it.
How can this be achieved using UITextView?
Basically you can take another approach for your problem by add a UIView or CALayer with a filled background to the left of your UITextView. Just set its width value to 2-3 to equate it as the 'left border'.
extension UITextView {
func setLeftBorder(color: UIColor, width:CGFloat = 2.0) {
let borderFrame = CGRect(x: 0, y: 0, width: width, height: self.frame.width)
let borderView = UIView(frame: borderFrame)
borderView.backgroundColor = color
self.addSubview(borderView)
}
}
But the problem will not solved when your UITextView starts scrolling. So I think you should embed them together inside another UIView. Here is my final solution by using AutoLayout:
My Solution - Click to see the image

Creating a view in IOS 10 Swift 3

Hi I want to create a view which looks something like below -
What I tried so far is I put two properties called image and text inside an UIView and tried to initialise the image view and text field and added in UIView I am able to see the image but not the text. May be I am missing proper constraints to put or something else. Can please someone help on what should be the best approach for this.
I would prefer it to be done without much involvement of storyboard i.e setting constraints for different views using storyboard. I am fine setting constraints in code. As you see it is a fairly reusable element and hence I want it as an class which can be assigned to any uiview and that should be it. Please correct me if something can be done better?
Thanks & Regards
Assumptions: the entire picture provided above is contained in a UIView and we are using Storyboard.
Refer to this picture for clarity in the explanation.
Make sure the outermost View has enough constraits so AutoLayout can properly size the view. If this doesn't happen, nothing past this point will matter.
Create 2 Views. Each has the same width as the container and is half the height. Place one in the upper half and one in the lower half. These are the ones with the blue and orange backgrounds.
Add an ImageView to the top View (blue background). Make the ImageView half the height of its Superview. Make the ImageView centered vertically in container. Add a constraint for the leading edge of the ImageView to be the same as the leading edge of the Superview margin. Add an AspectRatio constraint to the ImageView of 1:1.
Add a TextField to the top View (blue background). Center the TextField vertically in the container. Add a constraint for the HorizontalSpace between TextField leading and ImageView trailing and make the constant 8. Add a constraint for the TrailingSpace to the TextView for the SuperView trailing margin.
Change the Placeholder Text for the TextField to "Email"
Place the image in the imageview.
Repeat for the orange background View.
For the bottom borders, use this:
extension UIView{
func addBottomBorder(borderThickness: CGFloat, color: UIColor , widthPct: CGFloat) {
let border = UIView()
border.backgroundColor = color
border.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
var x: CGFloat = 0
let width = self.frame.size.width * widthPct
if widthPct < 1{
x = (self.frame.size.width - width) / 2
}
border.frame = CGRect(x: x, y: self.frame.size.height - borderThickness, width: width, height: borderThickness);
self.addSubview(border)
}
}

How to properly resize textview with exclusionPaths inside of table header view

I have added a view to my tableView header consisting of an imageView and a textView. The image view is left aligned in the top corner and the textview extends over the imageview to the right side of the screen like as follows.
The textView can have dynamic content and has an exclusion path set as follows:
let imagePath = UIBezierPath(rect: imageView.frame)
self.textView.textContainer.exclusionPaths = [imagePath]
I have disabled scrolling for the textview and have set the following constraints inside of the header view:
TextView: left - 8px, right - 8px, top - 0px, bottom - 8px
ImageView: left - 8px, width - 100px, height 100px, top - 8px, bottom - greater than or equal to 8px
I have added this code after my textView is populated with the dynamic text:
if let headerView = self.tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var headerFrame = headerView.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
self.tableView.tableHeaderView = headerView
}
}
Which adjusts the size of the header. However, when the textView has less text than the height of the image, the size of the view grows.
Example of three lines of text:
Example of six lines of text:
Example of enough text to pass imageview:
Does anyone know why this is happening?
I have a fix for this, because I just encountered this issue myself :)
You see, I was trying to do something very similar, where a UITextView's text should avoid a UIImageView to its left. My code was the following:
let ticketProfileExclusionPath = UIBezierPath(roundedRect: ticketProfilePicture.frame, cornerRadius: Constants.ProfilePictureExclusionRadius)
ticketContent.textContainer.exclusionPaths.append(ticketProfileExclusionPath)
And my results were not very good:
As you can see, the problem relies on the CGRect given to the ticketContent UITextView as its exclusion path, since the latter assumes the given CGRect is corrected to its own frame, not its superview's.
The fix is very simple and requires the use of an API present since the dawn of time (iOS 2.0):
let exclusionPathFrame = convert(ticketProfilePicture.frame, to: ticketContent).offsetBy(dx: Constants.SystemMargin, dy: Constants.Zero)
What we're doing here is converting the UIImageView's frame to the UITextView's coordinate system, therefore providing the correct exclusion path from the perspective of the UITextView. The added offset is simply to align all three of my UITableViewCell's text UIViews.
convert() is a UIView method.
As you can see, the text now wraps around the ticketProfilePicture UIImageView quite nicely.
Hope this helps.
For anyone using Auto Layout, make sure to add the exclusionPathFrame in viewDidLayoutSubviews().
You can calculate textview text height with content size.
let tempTextview = UITextView(frame: CGRect(x: 50, y: 50, width: 120, height: 250))
tempTextview.text = "Dynamic Text"
let height = tempTextview.contentSize.height
Add you extra top and bottom padding to this height.
height = height + padding
if imageview.frame.size.height > height
{
return imageview.frame.size.height
}
else
{
return height
}

Shrinking icon image in tableView cell

I have an array of png icons homeIcons[] that load into the cells of my tableView. Unfortunately, they are too big and want them to shrink a bit. Trying to shrink them with this code:
let cellIcon = UIImage(named: homeIcons[indexPath.row])
cell.imageView!.frame = CGRect(x: cell.imageView!.frame.origin.x, y: cell.imageView!.frame.origin.y, width: cell.imageView!.frame.size.width / 2, height: cell.imageView!.frame.size.height / 2)
cell.imageView!.image = cellIcon
Changing the frame width and height doesn't do anything. Is there anyway to change the size of my icons or am I forced to redraw my images?
Better if can add auto layout constraint to your imageview from storyboard.
or try calling layoutIfNeeded() after setting new frame.

Autolayout - intrinsic size of UIButton does not include title insets

If I have a UIButton arranged using autolayout, its size adjusts nicely to fit its content.
If I set an image as button.image, the instrinsic size again seems to account for this.
However, if I tweak the titleEdgeInsets of the button, the layout does not account for this and instead truncates the button title.
How can I ensure that the intrinsic width of the button accounts for the inset?
Edit:
I am using the following:
[self.backButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
The goal is to add some separation between the image and the text.
You can get this to work in Interface Builder (without writing any code), by using a combination of negative and positive Title and Content Insets.
Update: Xcode 7 has a bug where you cannot enter negative values in the Right Inset field, but you can use the stepper control next to it to decrease the value. (Thanks Stuart)
Doing this will add 8pt of spacing between the image and the title and will increase the intrinsic width of the button by the same amount. Like this:
You can solve this without having to override any methods or set an arbitrary width constraint. You can do it all in Interface Builder as follows.
Intrinsic button width is derived from the title width plus the icon width plus the left and right content edge insets.
If a button has both an image and text, they’re centered as a group, with no padding between.
If you add a left content inset, it’s calculated relative to the text, not the text + icon.
If you set a negative left image inset, the image is pulled out to the left but the overall button width is unaffected.
If you set a negative left image inset, the actual layout uses half that value. So to get a -20 point left inset, you must use a -40 point left inset value in Interface Builder.
So you provide a big enough left content inset to create space for both the desired left inset and the inner padding between the icon and the text, and then shift the icon left by doubling the amount of padding you want between the icon and the text. The result is a button with equal left and right content insets, and a text and icon pair that are centered as a group, with a specific amount of padding between them.
Some example values:
// Produces a button with the layout:
// |-20-icon-10-text-20-|
// AutoLayout intrinsic width works as you'd desire.
button.contentEdgeInsets = UIEdgeInsetsMake(10, 30, 10, 20)
button.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)
Why not override the intrinsicContentSize method on UIView? For example:
- (CGSize) intrinsicContentSize
{
CGSize s = [super intrinsicContentSize];
return CGSizeMake(s.width + self.titleEdgeInsets.left + self.titleEdgeInsets.right,
s.height + self.titleEdgeInsets.top + self.titleEdgeInsets.bottom);
}
This should tell the autolayout system that it should increase the size of the button to allow for the insets and show the full text. I'm not at my own computer, so I haven't tested this.
You haven't specified how you're setting the insets, so I'm guessing that you're using titleEdgeInsets because I see the same effect you're getting. If I use contentEdgeInsets instead it works properly.
- (IBAction)ChangeTitle:(UIButton *)sender {
self.button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
[self.button setTitle:#"Long Long Title" forState:UIControlStateNormal];
}
And for Swift worked this:
extension UIButton {
override open var intrinsicContentSize: CGSize {
let intrinsicContentSize = super.intrinsicContentSize
let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom
return CGSize(width: adjustedWidth, height: adjustedHeight)
}
}
Love U Swift
This thread is a bit old, but I just ran into this myself and was able to solve it by using a negative inset. For example, substitute your desired padding values here:
UIButton* myButton = [[UIButton alloc] init];
// setup some autolayout constraints here
myButton.titleEdgeInsets = UIEdgeInsetsMake(-desiredBottomPadding,
-desiredRightPadding,
-desiredTopPadding,
-desiredLeftPadding);
Combined with the right autolayout constraints, you end up with an auto-resizing button which contains an image and text! Seen below with desiredLeftPadding set to 10.
You can see that the actual frame of the button doesn't encompass the label (since the label is shifted 10 points to the right, outside the bounds), but we've achieved 10 points of padding between the text and the picture.
I wanted to add a 5pt space between my UIButton icon and the label. This is how I achieved it:
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
// more button config etc
infoButton.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 5);
infoButton.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, -5);
The way contentEdgeInsets, titleEdgeInsets and imageEdgeInsets relate to each other requires a little give and take from each inset. So if you add some insets to the title's left you have to add negative inset on the right and provide some more space (via a positive inset) on the content right.
By adding a right content inset to match the shift of the title insets my text doesn't go outside the bounds of the button.
For Swift 3 based on pegpeg's answer:
extension UIButton {
override open var intrinsicContentSize: CGSize {
let intrinsicContentSize = super.intrinsicContentSize
let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom
return CGSize(width: adjustedWidth, height: adjustedHeight)
}
}
All above did not work for iOS 9+, what i did is:
Add a width constraint (for a minimum width when the button doesn't have any text. The button will auto scale if text is provided)
set the relation to Greater Than or Equal
Now to add a border around the button just use the method:
button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
The option is also available in interface builder. See the Inset. I set left and right to 3. Works like a charm.
The solution I use is to add a width constraint on the button. Then somewhere in initialization, after your text is set, update the width constraint like so:
self.buttonWidthConstraint.constant = self.shareButton.intrinsicContentSize.width + 8;
Where 8 is whatever your inset is.

Resources