clipsToBounds causes UIImage to not display in iOS10 & XCode 8 - ios

I switched my project over to new beta versions of iOS 10 and XCode 8. In all three areas of my app where I use:
imageView.layer.cornerRadius = imageView.frame.size.width/2
imageView.clipsToBounds = true
The associated images are not displaying at all. I have attempted cleaning the project as well as the build folder, restarting the device, trying on various simulators, re-adding the imageView, programmatically setting the associated UIImage instead of choosing one from the assets.
Removing the clipsToBounds line shows the rectangular image regardless of whether masksToBounds is true or false. How can I make a circular image in XCode8 / iOS10 ?
Edit: The project is Swift 2.x and not yet updated to Swift 3.0 syntax.

I had the same problem and calling layoutIfNeeded before all the rounded corner / clipsToBounds stuff fixed the issue. iOS 10 GM with xcode 8 GM causes views to disappear due to roundedCorners & clipsToBounds

This problem also happened to me.
I moved these code imageView.layer.cornerRadius = imageView.frame.size.width/2 from - (void)awakeFromNib to - (void)drawRect:(CGRect)rect and this problem went away.
My imageView is sized by autolayout. I think that height and width are not decided when awaking from nib on iOS 10.

This sounds like it could be due to a new bug since iOS 10 and Xcode 8 where views are initialised at size 1000x1000 and when trying to set corner radius to half your frame, it is setting it to 500 instead. This is documented further in this question here: Since Xcode 8 and iOS10, views are not sized properly on viewDidLayoutSubviews. My reason for thinking this is the fix to the 1000x1000 issue it to call layout subviews before doing anything that requires sizes for something that has been constructed on the interface builder.

I had the same problem. Not showing on the phone, but perfect on Storyboard and UI debugger. It was also working when I was putting the project "Opens with Xcode 7" instead of 8 but wasn't a satisfying solution. So I digged and found out the problem. The trouble was with a class looking like this :
#IBDesignable public class MyButton: UIButton {
#IBInspectable var styleName: String = "" {
didSet {
switch styleName {
case "RoundedLight":
tintColor = UIColor.lightPinkColor()
layer.borderColor = UIColor.whiteColor().CGColor
layer.borderWidth = 1
layer.masksToBounds = true
default:
break
}
}
}
override public func layoutSubviews() {
super.layoutSubviews()
switch styleName {
case "RoundedLight":
layer.cornerRadius = frame.height / 2
default:
break
}
}
}
I changed it to this and it works now :
#IBDesignable public class MyButton: UIButton {
#IBInspectable var styleName: String = "" {
didSet {
layoutIfNeeded()
}
}
override public func layoutSubviews() {
super.layoutSubviews()
switch styleName {
case "RoundedLight":
tintColor = UIColor.lightPinkColor()
layer.borderColor = UIColor.whiteColor().CGColor
layer.borderWidth = 1
layer.cornerRadius = frame.height / 2
layer.masksToBounds = true
default:
break
}
}
}
Note that none of the above worked for me, and I mean :
Calling layoutIfNeeded in the layoutSubviews()
Calling layoutIfNeeded in the awakeFromNib of my button (or my cell containing the button)
Calling layoutIfNeeded in the layoutSubview of my cell
Calling contentView.layoutIfNeeded() in the awakeFromNib of my cell
Calling view.layoutIfNeeded() in my view controller

I had ImageView that is fully circular in shape. Below is code :
//MARK:- Misc functions
func setProfileImage() {
imgProfile.layer.masksToBounds = false
imgProfile.layer.cornerRadius = imgProfile.frame.size.height/2
imgProfile.clipsToBounds = true
imgProfile.contentMode = UIViewContentMode.ScaleToFill
}
This works fine in iOS 9. However, in iOS 10 Xcode 8 the ImageView disappears.
After debugging found that ClipsToBound is culprit.
So placed the code in viewDidLayoutSubviews() resolved the issue.
override func viewDidLayoutSubviews() {
setProfileImage()
}
You can also use self.view.layoutIfNeeded()

It might be a layout issue. Setting clipToBounds to false would show the image even if its size is zero. Can you print the frame of your images?
If you set the cornerRadius and clipToBounds properties in viewDidLoad, try doing it in viewDidLayoutSubviews().
You could also try to call self.view.layoutIfNeeded().

Using obj-c, moving the syntax from viewDidLoad to viewDidLayoutSubviews worked for me.

I just implemented corner rounding like this
#implementation RoundImageView
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
self.layer.masksToBounds = YES;
self.layer.cornerRadius = MIN(self.bounds.size.height, self.bounds.size.width)/2;
[self addObserver:self
forKeyPath:#"bounds"
options:NSKeyValueObservingOptionNew
context:(__bridge void * _Nullable)(self)];
}
return self;
}
-(void)dealloc
{
[self removeObserver:self
forKeyPath:#"bounds"
context:(__bridge void * _Nullable)(self)];
}
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context
{
if(context == (__bridge void * _Nullable)(self) && object == self && [keyPath isEqualToString:#"bounds"])
{
self.layer.cornerRadius = MIN(self.bounds.size.height, self.bounds.size.width)/2;
}
}
#end
so I always have properly rounded corners.
And I hadn't issues on upgrading to iOS10 and XCode8

I think the problem happens because we set the round corner and clip subviews as #Pranoy C said.
I solved the problems by using layoutIfNeeded for the my tableview cell showing a user's profile picture. I want to ensure the image is round corner
Code is as follow:
- (void)awakeFromNib {
[super awakeFromNib];
[self layoutIfNeeded];
[self.inputIndicator_imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
self.profile_pic_edit_imageView.layer.cornerRadius = self.profile_pic_edit_imageView.frame.size.width/2;
self.profile_pic_edit_imageView.layer.borderWidth = 1.0;
self.profile_pic_edit_imageView.layer.borderColor = GRAY_COLOR_SUPER_LIGHT.CGColor;
self.profile_pic_edit_imageView.clipsToBounds = YES;}

You should call layoutSubviews() on the main view before call imageView.frame.size.width
[self.view layoutSubviews];
imageView.layer.cornerRadius = imageView.frame.size.width/2
imageView.clipsToBounds = true

Related

UISearchBar wont remove UISearchBarBackground

I have tried EVERYTHING to get this to work. I setup a custom class like so.
override func layoutSubviews() {
super.layoutSubviews()
clearBackgroundColor() // function in the question
}
private func clearBackgroundColor() {
guard let UISearchBarBackground: AnyClass = NSClassFromString("UISearchBarBackground") else { return }
for view in self.subviews {
for subview in view.subviews {
if subview.isKind(of: UISearchBarBackground) {
subview.alpha = 0
}
}
}
}
I set backgroundColor, barTintColor to .clear. Style to minimal. Im losing my mind. I set breakpoints to make sure we are finding the search bar background. Ive tried subview.removeFromSuperview() as well. Nothing. I think Im going insane. Am I missing something?
This is on iOS 10 and am using storyboard. Any help would be greatly appreciated.
I had to do this in a client's app a while ago. Here's what worked for me:
I had a UISearchBar subclass:
#property (nonatomic, strong) UITextField* textField;
I called the following from init:
self.textField = [self findViewOfClass:[UITextField class] inView:self];
self.translucent = NO;
self.barTintColor = ...;
self.textField.backgroundColor = ...;
- (id)findViewOfClass:(Class)class inView:(UIView*)view
{
if ([view isKindOfClass:class])
{
return view;
}
else
{
for (UIView* subview in view.subviews)
{
id foundView = [self findViewOfClass:class inView:subview];
if (foundView != nil)
{
return foundView;
}
}
}
return nil;
}
The essential part is finding the UITextField. (I did a similar thing to allow me to custom style the cancel button.) I vaguely remember that disabling translucent was really needed; easy to try.
That should be it. Let me know if this works for you.
I only have Obj-C code, but this is easy to convert.
I finally ignored previous answers from all the posts about this subject and did my own Debug View Hierarchy. I spotted a ImageView that serves as the background which I guess is now called "_UISearchBarSearchFieldBackgroundView". This helped me find a single function that fixes the problem at least for iOS 9+.
searchBar.setSearchFieldBackgroundImage(UIImage(), for: .normal)
One thing to note is that this isn't the only way to fix this problem. However, I used it because it requires no looping and because the image is empty the additional view is never added giving the same end result as other methods.
One thing to note is that this may only work for iOS 9+. So, your milage may vary. I tested with iOS 10 with a Deployment Target of 9.3.

hide the inputToolbar in JSQMessagesViewController

I am using JSQMessagesViewController for my chat application. When there is no internet activity I would like to hide the inputToolbar
I tried this, but that does not work:
self.inputToolbar.frame.size = CGSize(width: 0,height: 0)
When I set this, then for less than a second it's gone:
self.inputToolbar.preferredDefaultHeight = 0
Any idea how to do this?
Maybe disabling the inputToolbar could also be good enough.
I found a better solution which doesn't have any side effects.
You can make the actions in a descendant class of JSQMessagesViewController.
1. Make this method of base class available for you:
#interface JSQMessagesViewController ()
- (void)jsq_setCollectionViewInsetsTopValue:(CGFloat)top
bottomValue:(CGFloat)bottom;
#end
2. Override parent realization of method (called when size changed):
- (void)jsq_updateCollectionViewInsets {
CGFloat topInset = self.topLayoutGuide.length + self.topContentAdditionalInset;
CGFloat bottomInset = 0.0;
[self jsq_setCollectionViewInsetsTopValue:topInset bottomValue:bottomInset];
}
3. Write the method to hide input toolbar forever:
- (void)hideInputToolbar {
self.inputToolbar.hidden = YES;
[self jsq_updateCollectionViewInsets];
}
4. Enjoy!
Instead of removing from superview and having to add back as a subview, why not just use:
[self.inputToolbar setHidden:YES];
It turned out that this will work:
override func viewDidLoad() {
super.viewDidLoad()
self.inputToolbar.removeFromSuperview()
}

Programmatic Device Specific iOS Constraint is nil

I came across an interesting problem that only arises on iPhone 6/6+ and iPad mini with retina display.
In the following code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(self.seeMoreContents)
{
BOOL isText = [self.seeMoreContents isText];
self.imageView.hidden = isText;
[self.textView removeConstraint:self.textHeightConstraint];
[self.textWrapperView removeConstraint:self.textWrapperHeightConstraint];
if (!isText)
{
__weak FCSeeMoreViewController *weakSelf = self;
[self.imageView setImageByFlashcardSide:self.seeMoreContents completion:^(BOOL preloaded){
weakSelf.imageView.center = CGPointMake(weakSelf.view.frame.size.width / 2, weakSelf.view.frame.size.height / 2);
[weakSelf.scrollView setContentOffset:CGPointMake(0, 0)];
}];
}
}
}
- (void)viewDidLayoutSubviews
{
if ([self.seeMoreContents isText])
{
self.textView.text = self.seeMoreContents.text;
self.textView.font = self.fontForContents;
self.textWrapperView.hidden = NO;
[self.textView sizeToFit];
CGFloat height = self.textView.frame.size.height;
[self updateView:self.textView withConstraint:self.textHeightConstraint ofValue:height];
[self updateView:self.textWrapperView withConstraint:self.textWrapperHeightConstraint ofValue:height + self.wrapperMargin];
[self.scrollView setContentSize:CGSizeMake(self.textView.frame.size.width, height + self.scrollTextMargin)];
[self.scrollView setContentOffset:CGPointMake(0, -self.wrapperScrollVerticalConstraint.constant)];
}
[super viewDidLayoutSubviews];
}
- (void)updateView:(UIView*)view withConstraint:(NSLayoutConstraint*)constraint ofValue:(CGFloat)value
{
constraint.constant = value;
[view addConstraint:constraint];
}
By the time the two messages of udpateView get passed, the constraints have become nil. I could attribute this to weird garbage collection behavior, but it only happens on iPhone 6/6+ and mini retina iPad.
I have changed this whole controller to work better and to not to programmatically set constraints, but I want to know how/why this can happen on specific devices. Any insight would be greatly appreciated.
Override this method in your UIViewController to detect changing of 'traits':
func willTransitionToTraitCollection(_ newCollection: UITraitCollection,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
This is where you intercept constraint changes to get them at the right time, otherwise it's a race and your code can lose.
I suspect not using that function to get the timing right may be why you are not seeing consistent results. I bumped into the same kind of problem awhile back - not finding constraints that should have been there when I went looking for them.
Another thing to consider about mysterious constraints appearing and disappearing, of course, is UIView's
translatesAutoresizingMaskIntoConstraints
property, which if true (the default), causes iOS to dynamically create constraints, besides whatever you may have created programmatically or created in Interface Builder. And I have noticed some iOS generated constraints can disappear in different devices and orientations, as the iOS implementation that applies and removes such constraints is a black box.

left circle in UITableViewCell editing mode appears in iOS8

So I just installed Xcode 6GM and fiddled with my iOS7 app on simulator running iOS8.
I have a UITableView that's in editing mode and there's now a circle on the left side of the cell which doesn't appear when running on iOS7.
I glanced at the documentation for iOS8, but I don't see any new constants and I'm using UITableViewCellEditingStyleNone and UITableViewCellSelectionStyleNone.
That circle disappears when tableView.editing = NO, also allowsMultipleSelectionDuringEditing = YES.
If anyone can tell me what's going on that'd be great :)
EDIT: compiling from XCode6GM onto my iPhone running iOS7.1 gives me the circle too. I suspect a bug with XCode6GM?
Here is a screenshot with the circles:
I just had this annoying issue while migrating my app to iOS8.
Here is the workaround I found ... add something like this in your UITableViewCell subclass:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
for( UIView* subview in self.subviews )
if( [NSStringFromClass(subview.class) isEqualToString:#"UITableViewCellEditControl"] )
subview.hidden = YES;
}
I hope this will be documented / fixed soon ...
I think I have a better solution, add this code to your custom uitableviewcell:
- (void)addSubview:(UIView *)view {
[super addSubview:view];
if( [NSStringFromClass(view.class) isEqualToString:#"UITableViewCellEditControl"] ) {
view.hidden = YES
}
}
Here's a Swift solution combining the two answers:
override func addSubview(view: UIView) {
super.addSubview(view)
if view.isKindOfClass(NSClassFromString("UITableViewCellEditControl")!) {
view.hidden = true
}
}
Here is the Swift3 version:
override func addSubview(_ view: UIView) {
super.addSubview(view)
if view.classAsString() == "UITableViewCellEditControl" {
view.isHidden = true
}
}

Modify UIImage renderingMode from a storyboard/xib file

Is it possible to modify a UIImage's renderingMode from a storyboard or xib editor?
The goal is to apply tintColor to the particular UIImageView object.
You can set the image rendering mode not in the .xib file, but in an .xcassets library.
After adding an image to an asset library, select the image and open the attributes inspector on the right side of Xcode. Find the attribute 'Render As' and set it to 'template'.
After setting an image's rendering mode, you can add a tint color to the UIImageView in a .xib or .storyboard file to adjust the image color.
This sets the property on the image wherever it's used rather than just in one interface builder file, but in almost all cases (that I've encountered) this is the behavior you want.
A few things to note:
The image color will not appear to have changed in interface builder (as of Xcode 6.1.1) but will work when the application is run.
I've experienced some bugginess with this feature and in some situations I've had to remove and re-add the UIImageView. I have not looked into that deeply.
This also works great on other UIKitComponents such as images in UIButton's and UIBarButtonItem's.
If you have a bunch of white images that are invisible in your asset library, making them black/transparent images and changing the rendering mode will make your life up to 10x better.
Here's how you can do it in .xib or storyboard files:
(Obj-C) Create a category on UIImageView:
#interface UIImageView (Utils)
- (void)setImageRenderingMode:(UIImageRenderingMode)renderMode;
#end
#implementation UIImageView (Utils)
- (void)setImageRenderingMode:(UIImageRenderingMode)renderMode
{
NSAssert(self.image, #"Image must be set before setting rendering mode");
self.image = [self.image imageWithRenderingMode:renderMode];
}
#end
(Swift 4) Create an extension for UIImageView:
extension UIImageView {
func setImageRenderingMode(_ renderMode: UIImage.RenderingMode) {
assert(image != nil, "Image must be set before setting rendering mode")
// AlwaysOriginal as an example
image = image?.withRenderingMode(.alwaysOriginal)
}
}
Then in the Identity Inspector in the xib file, add a runtime attribute:
Using the template rendering mode with a UIImageView in a storyboard or xib is very buggy, both on iOS 7 and iOS 8.
On iOS 7
The UIImage is not properly decoded from the storyboard/xib. If you inspect the imageView.image.renderingMode property in the viewDidLoad method, you will notice that it is always UIImageRenderingModeAutomatic, even if you set it to Render As Template Image in your xcassets file.
To workaround, you have to manually set the rendering mode:
self.imageView.image = [self.imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
On iOS 8
The UIImage is properly decoded and its renderingMode property reflects what was chosen in the xcassets file but the image is not tinted.
To workaround, you have two options:
Set the tintColor property in the User Defined Runtime Attributes instead of the Attributes inspector pane.
or
Manually reset the tintColor:
UIColor *tintColor = self.imageView.tintColor;
self.imageView.tintColor = nil;
self.imageView.tintColor = tintColor;
You can pick your preferred option, both properly tint the image.
(If you are compiling with Xcode 6.2, just doing self.imageView.tintColor = self.imageView.tintColor; is enough but this doesn’t work anymore if you are compiling with Xcode 6.3)
Conclusion
If you need to support both iOS 7 and iOS 8, you’ll need both workarounds. If you only have to support iOS 8, only one workaround is needed.
Setting imageView RenderingMode to use the tint color in the storyboard can be reduced to a one-liner:
[self.imageView setImage:[self.imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
Then the image and tint color can all be set in the Storyboard.
You may fix .xib issues with an extension:
import UIKit
// fixing Bug in XCode
// http://openradar.appspot.com/18448072
extension UIImageView {
override open func awakeFromNib() {
super.awakeFromNib()
self.tintColorDidChange()
}
}
Source: https://gist.github.com/buechner/3b97000a6570a2bfbc99c005cb010bac
Amazing, this bug has been around for like 4-5 years now.
You cann't set renderingMode either from storyboard or xib. It could access by programmatically.
ex:
UIImage *unSeletedImage = [UIImage imageNamed:#"UnSelected.png"];
selectedImage = [selectedImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
Set tintColor & Class in Storyboard.
//
// TintColoredImageView.swift
// TintColoredImageView
//
// Created by Dmitry Utmanov on 14/07/16.
// Copyright © 2016 Dmitry Utmanov. All rights reserved.
//
import UIKit
#IBDesignable class TintColoredImageView: UIImageView {
override var image: UIImage? {
didSet {
let _tintColor = self.tintColor
self.tintColor = nil
self.tintColor = _tintColor
}
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
override init(image: UIImage?) {
super.init(image: image)
initialize()
}
override init(image: UIImage?, highlightedImage: UIImage?) {
super.init(image: image, highlightedImage: highlightedImage)
initialize()
}
func initialize() {
let _tintColor = self.tintColor
self.tintColor = nil
self.tintColor = _tintColor
}
}
It's very easy to fix
Just create class UIImageViewPDF and use it in your storyboard
IB_DESIGNABLE
#interface UIImageViewPDF : UIImageView
#end
#implementation UIImageViewPDF
- (void) didMoveToSuperview
{
[super didMoveToSuperview];
self.image = [self.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
id color = self.tintColor;
self.tintColor = color;
}
#end
Another solution is to create a UIImageView subclass:
final class TemplateImageView: UIImageView {
override func awakeFromNib() {
super.awakeFromNib()
guard let oldImage = image else { return }
image = nil
image = oldImage.withRenderingMode(.alwaysTemplate)
}
}
Then just set the class in the Interface Builder to TemplateImageView.
Simple way to be set from Storyboard:
#IBDesignable
public class CustomImageView: UIImageView {
#IBInspectable var alwaysTemplate: Bool = false {
didSet {
if alwaysTemplate {
self.image = self.image?.withRenderingMode(.alwaysTemplate)
} else {
self.image = self.image?.withRenderingMode(.alwaysOriginal)
}
}
}
}
Works fine on iOS 10 and Swift 3
I got fixed this issue by adding runtime attribute tintColor in interface builder.
NOTE : You will still need to set your image to be rendered as a template image in your Images.xcassets file.
In iOS 9 setting the tintColor property in Interface Builder is still buggy.
Note that a working solution besides writing lines directly modifying ImageView properties is to set Render As: Template Image in the asset catalog, and call e.g.:
[[UIImageView appearanceWhenContainedInInstancesOfClasses:#[[MyView class]]] setTintColor:[UIColor whiteColor]];
If you create an IBOutlet you can change it in your awakeFromNib method like so...
self.myImageView.image = [self.myImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
While #Moby's answer is more correct - this might be more succinct.
Crazy this bug is still in iOS 12.1! For storyboards/xibs: Adding a tag to the UIImageView can be a quick fix.
Swift 4.2
view.viewWithTag(1)?.tintColorDidChange()
extension UIImageView {
#IBInspectable var renderModeTemplate : Bool {
get{
return image?.renderingMode == .alwaysTemplate
}
set{
image = image?.withRenderingMode(newValue ? .alwaysTemplate:.alwaysOriginal)
}
}
}
In storyboard select UIImageView and select inspector, set property renderModeTemplate = On
In Storyboard
As Rudolf also mentioned above, I would define a simple class, like this:
import UIKit
#IBDesignable class TintImage: UIImageView{
override func layoutSubviews() {
super.layoutSubviews()
image = image?.withRenderingMode(.alwaysTemplate)
}
}
After this definition, just add an Image View to storyboard and select its custom class as TintImage. This will activate the "Tint" selection in the storyboard.

Resources