UIWebView rendering web pages in a low-quality zoomed in state - ios

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!

Related

How do I add constraints to a subview loaded from a nib file?

I'm trying to load a sub view on to every single page of my app from a nib file. Right now I'm using a somewhat unusual approach to loading this sub view in that I am doing it through an extension of UIStoryboard (probably not relevant to my problem, but I'm not sure). So this is how the code looks when I load the nib file:
extension UIStoryboard {
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
viewController.view.addSubview(myCustomSubview)
}
}
}
This code does what it's supposed to do and adds "MyCustomSubview" to the view controller (I won't go in to detail on exactly how this method gets called because it works so it doesn't seem important). The problem is I can't for the life of me figure out how to add constraints that effect the size of myCustomSubview. I have tried putting code in the function I showed above as well as in the MyCustomSubview swift file to add constraints but no matter what I do the subview never changes.
Ideally the constraints would pin "MyCustomSubview" to the bottom of the ViewController, with width set to the size of the screen and a hard coded height constraint.
Here are the two main methods I tried (with about 100 minor variations for each) that did NOT work:
Method 1 - Add constraint directly from "appendCustomView"
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
let top = NSLayoutConstraint(item: myCustomSubview, attribute: .top, relatedBy: .equal
, toItem: viewController.view, attribute: .top, multiplier: 1, constant: 50.0)
viewController.view.addSubview(myCustomSubview)
viewController.view.addConstraint(top)
}
}
Method 2 - Add constraint outlets and setter method in MyCustomSubview
class MyCustomSubview: UIView {
#IBOutlet weak var widthConstraint: NSLayoutConstraint!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
func setConstraints(){
self.widthConstraint.constant = UIScreen.main.bounds.size.width
self.heightConstraint.constant = 20
}
}
And call setter method in "appendCustomView"
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
myCustomSubview.setConstraints()
viewController.view.addSubview(myCustomSubview)
}
}
(*note: the actual constraints of these examples are irrelevant and I wasn't trying to meet the specs I mentioned above, I was just trying to make any sort of change to the view to know that the constraints were updating. They weren't.)
Edit : Changed "MyCustomNib" to "MyCustomSubview" for clarity.
When you add constraints onto a view from a Nib, you have to call yourView.translatesAutoresizingMaskIntoConstraints = false, and you also need to make sure that you have all 4 (unless it's a label or a few other view types which only need 2) constraints in place:
Here's some sample code that makes a view fill it's parent view:
parentView.addSubview(yourView)
yourView.translatesAutoresizingMaskIntoConstraints = false
yourView.topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true
yourView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true
yourView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor).isActive = true
yourView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor).isActive = true
Edit: I've actually come around to perferring this method of adding NSLayoutConstraints, even though the results are the same
NSLayoutConstraint.activate([
yourView.topAnchor.constraint(equalTo: parentView.topAnchor),
yourView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
yourView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor),
yourView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
])
For anyone who comes across this in the future, this is the solution I came up with by tweaking this answer a little bit
Add a setConstraints(withRelationshipTo) method in the swift class that corresponds to the nib file:
class MyCustomSubview: UIView {
func setConstraints(withRelationshipTo view: UIView){
self.translatesAutoresizingMaskIntoConstraints = false
// Replace with your own custom constraints
self.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
self.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
Then call the setConstraints method after you add the nib file to the view (probably in viewWillAppear or viewDidLoad of a view controller )
class MyViewController: UIViewController {
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
let view = self.view // Added for clarity
view.addSubview(myCustomSubview)
myCustomSubview.setConstraints(withRelationshipTo: view)
}
}
}
You can use this extension for anywhere you're going to add a subview to a existing UIView.
extension UIView {
func setConstraintsFor(contentView: UIView, left: Bool = true, top: Bool = true, right: Bool = true, bottom: Bool = true) {
contentView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(contentView)
var constraints : [NSLayoutConstraint] = []
if left {
let constraintLeft = NSLayoutConstraint(item: contentView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0)
constraints.append(constraintLeft)
}
if top {
let constraintTop = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0)
constraints.append(constraintTop)
}
if right {
let constraintRight = NSLayoutConstraint(item: contentView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0)
constraints.append(constraintRight)
}
if bottom {
let constraintBottom = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
constraints.append(constraintBottom)
}
self.addConstraints(constraints)
}
}
You can call this method like this:
containerView.setConstraintsFor(contentView: subView!, top: false)
This will add subView to the containerView and constraint to all sides except top side.
You can modify this method to pass left, top, right, bottom Constant value if you want.

How to make UIImageView display properly? SWIFT [duplicate]

I'm trying to make a UIView image for my background in swift using pattern image. The code I have works well except for the fact that I want the image to take the whole screen. My code looks like this: self.view.backgroundColor = UIColor(patternImage: UIImage(named: "backgroundImage")!)
Does anyone know how to make the background an image that will take up the whole screen, and would scale when appearing on different iPhone screen sizes?
Note That:
I posted this answer from my old account (which is deprecated for me and I can't access it anymore), this is my improved answer.
You can do it programmatically instead of creating an IBOutlet in each view.
just create a UIView extension (File -> New -> File -> Swift File -> name it whatever you want) and add:
extension UIView {
func addBackground() {
// screen width and height:
let width = UIScreen.mainScreen().bounds.size.width
let height = UIScreen.mainScreen().bounds.size.height
let imageViewBackground = UIImageView(frame: CGRectMake(0, 0, width, height))
imageViewBackground.image = UIImage(named: "YOUR IMAGE NAME GOES HERE")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.ScaleAspectFill
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}}
Now, you can use this method with your views, for example:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addBackground()
}
Just add your UIImageView positioned
centered and with all edges snapping to the edges. Leave it there and
click on the right bottom corner as shown below and now go ahead and
add 4 constrains to Top, Bottom, Left and Right Edges.
Now just select your image view and using the IB inspector select how
you would like your image: fill or fit as you can see as follow:
This is the updated answer of my previous one.
As the same approach of my previous answer, You can create an extension of UIView and add addBackground() method to it, as follows:
Remember: if you are adding it in a new .swift file, remember to add import UIKit
extension UIView {
func addBackground(imageName: String = "YOUR DEFAULT IMAGE NAME", contentMode: UIView.ContentMode = .scaleToFill) {
// setup the UIImageView
let backgroundImageView = UIImageView(frame: UIScreen.main.bounds)
backgroundImageView.image = UIImage(named: imageName)
backgroundImageView.contentMode = contentMode
backgroundImageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(backgroundImageView)
sendSubviewToBack(backgroundImageView)
// adding NSLayoutConstraints
let leadingConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 0.0)
let trailingConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let topConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0)
NSLayoutConstraint.activate([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
}
}
Note that the updates for this answer are:
Swift 4 code 🙂
Adding -programatically- NSLayoutConstraints: that's because when applying what's mentioned in my previous answer, it works fine for the current device orientation, but not when the application does support both portrait/landscape modes, if the device orientation has been changed, the background imageView size will be the same (same size) and not adapts the new width/height of the device screen, so adding constraints should solve this issue.
Adding default parameters: for more flexibility, you might -sometimes- want to change the default image or even the context mode for you background:
Usage:
Assuming that you want to call it in viewDidLoad():
override func viewDidLoad() {
//...
// you can call 4 versions of addBackground() method
// 1- this will add it with the default imageName and default contextMode
view.addBackground()
// 2- this will add it with the edited imageName and default contextMode
view.addBackground(imageName: "NEW IMAGE NAME")
// 3- this will add it with the default imageName and edited contextMode
view.addBackground(contentMode: .scaleAspectFit)
// 4- this will add it with the default imageName and edited contextMode
view.addBackground(imageName: "NEW IMAGE NAME", contextMode: .scaleAspectFit)
}
Here are your options for scaling!
For the .contentMode property:
ScaleToFill
This will scale the image inside the image view to fill the entire boundaries of the image view.
ScaleAspectFit
This will make sure the image inside the image view will have the right aspect ratio and fit inside the image view’s boundaries.
ScaleAspectFill
This will make sure the image inside the image view will have the right aspect ratio and fill the entire boundaries of the image view.
For this value to work properly, make sure that you have set the clipsToBounds property of the imageview to true.
class SecondViewController : UIViewController {
let backgroundImage = UIImage(named: "centralPark")
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.thirdChoiceField.delegate = self
self.datePicker.minimumDate = NSDate()
imageView = UIImageView(frame: view.bounds)
imageView.contentMode = .ScaleAspectFill
imageView.clipsToBounds = true
imageView.image = backgroundImage
imageView.center = view.center
view.addSubview(imageView)
self.view.sendSubviewToBack(imageView)
me showing my answer as which Lines of code helps me...
extension file code ....
extension UIView {
func addBackground() {
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
imageViewBackground.image = UIImage(named: "Your Background Image Name")
imageViewBackground.contentMode = .scaleAspectFill
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}
}
and in view controller file....
override func viewDidLoad() {
super.viewDidLoad()
view.addBackground()
}
Thank You Guys !
Ahmad Fayyas Solution in Swift 3.0:
func addBackground() {
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x:0, y:0, width: width, height: height))
imageViewBackground.image = UIImage(named: "YOUR IMAGE NAME GOES HERE")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.scaleAspectFill
self.view.addSubview(imageViewBackground)
self.view.sendSubview(toBack: imageViewBackground)
}
This uses PureLayout. You could just use AutoLayout with a few more lines.
UIImageView* imgView = UIImageView(image: myUIImage)
imgView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(imgView)
self.view.addConstraints(imgView.autoPinEdgesToSuperviewEdgesWithInsets(UIEdgeInsetsMake(0,0,0,0))
I used constraints to make the image "autoLayout". I made a view to show an activity indicator (with full background image), while the view on segue is loading. The code is as follows.
var containerView: UIView = UIView()
var actionIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
private func showActivityIndicator() {
///first I set the containerView and the background image
containerView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(containerView)
adjustConstFullSize(containerView, parentView: self.view)
let backImage = UIImageView(image: UIImage(named: "AppBackImage"))
backImage.contentMode = UIViewContentMode.ScaleAspectFill
backImage.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(backImage)
adjustConstFullSize(backImage, parentView: containerView)
////setting the spinner (activity indicator)
actionIndicator.frame = CGRectMake(0.0, 0.0, 40.0, 40.0)
actionIndicator.center = CGPointMake(containerView.bounds.size.width / 2, containerView.bounds.size.height / 2)
actionIndicator.hidesWhenStopped = true
actionIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
containerView.insertSubview(actionIndicator, aboveSubview: backImage)
///throw the container to the main view
view.addSubview(containerView)
actionIndicator.startAnimating()
}
This is the code for the "adjustConstFullSize" function.
func adjustConstFullSize(adjustedView: UIView!, parentView: UIView!) {
let topConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Top,
relatedBy: .Equal,
toItem: parentView,
attribute: .Top,
multiplier: 1.0,
constant: 0.0)
let leftConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Leading,
relatedBy: .Equal,
toItem: parentView,
attribute: .Leading,
multiplier: 1.0,
constant: 0.0)
let rightConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Trailing,
relatedBy: .Equal,
toItem: parentView,
attribute: .Trailing,
multiplier: 1.0,
constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Bottom,
relatedBy: .Equal,
toItem: parentView,
attribute: .Bottom,
multiplier: 1.0,
constant: 0.0)
parentView.addConstraints([topConstraint, leftConstraint, rightConstraint, bottomConstraint])
}
In the function shown above, I "tied" the containerView constraints to the main view constraints, making the view "full size". I did the same for the UIImageView and also set the contentMode to AspectFill - this is crucial, because we want the image to fill the content without stretching.
To remove the view, after the lazy loading, just use the code below.
private func hideActivityIndicator() {
actionIndicator.stopAnimating()
containerView.removeFromSuperview()
}
For this, I think you'll need to create a UIImageView that is pinned to the parent views top / bottom / left / right. This will make the UIImageView always the match the size of the display. Just make sure you set the content mode on the imageview to be AspectFit
var newImgThumb : UIImageView
newImgThumb = UIImageView(view.bounds)
newImgThumb.contentMode = .ScaleAspectFit
view.addSubview(newImgThumb)
//Don't forget this line
newImgThumb.setTranslatesAutoresizingMaskIntoConstraints(false)
NSDictionary *views =NSDictionaryOfVariableBindings(newImgThumb);
// imageview fills the width of its superview
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[newImgThumb]|" options:0 metrics:metrics views:views]];
// imageview fills the height of its superview
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[newImgThumb]|" options:0 metrics:metrics views:views]];
`
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
_imgBackGround.frame = CGRectMake(0, 0, screenWidth, screenHeight);`

UIViews overlapping when adding multiple views dynamically to a tableViewCell in swift

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.

UIView "Stuck", not responding to selectors

I'm adding a custom UIView to my HomeViewController's view like so:
var customView: CustomView!
func addCustomView() {
customView = CustomView(frame: CGRect(x: 0, y: 0, width: Int(view.frame.width), height: 150.0))
customView.translatesAutoresizingMaskIntoConstraints = false
customView.delegate = self
view.addSubview(customView)
self.view.addConstraint(NSLayoutConstraint(item: customView, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[view]-20-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["view": customView]));
}
This custom view has a button that segues to a modal view controller, and sets the current view controller as its delegate. When I dismiss the modal view controller, it calls its delegate method:
func modalVCDidCreateThing(thing: PFObject) {
customView.removeFromSuperview()
}
The customView is not being removed from the superview.
I've checked that it's not nil
After performing the removeFromSuperview, logging out the object shows that it still exists
The code is running on the main thread
The view doesn't respond to other commands either. For example, I tried setting it's hidden property to false, but the view does not hide. Logging out the view showed that the property was set.
I tried view.setNeedsLayout after removeFromSuperview. Didn't work.
Super frustrated by this. Thanks.

How do you make a background image scale to screen size in swift?

I'm trying to make a UIView image for my background in swift using pattern image. The code I have works well except for the fact that I want the image to take the whole screen. My code looks like this: self.view.backgroundColor = UIColor(patternImage: UIImage(named: "backgroundImage")!)
Does anyone know how to make the background an image that will take up the whole screen, and would scale when appearing on different iPhone screen sizes?
Note That:
I posted this answer from my old account (which is deprecated for me and I can't access it anymore), this is my improved answer.
You can do it programmatically instead of creating an IBOutlet in each view.
just create a UIView extension (File -> New -> File -> Swift File -> name it whatever you want) and add:
extension UIView {
func addBackground() {
// screen width and height:
let width = UIScreen.mainScreen().bounds.size.width
let height = UIScreen.mainScreen().bounds.size.height
let imageViewBackground = UIImageView(frame: CGRectMake(0, 0, width, height))
imageViewBackground.image = UIImage(named: "YOUR IMAGE NAME GOES HERE")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.ScaleAspectFill
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}}
Now, you can use this method with your views, for example:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addBackground()
}
Just add your UIImageView positioned
centered and with all edges snapping to the edges. Leave it there and
click on the right bottom corner as shown below and now go ahead and
add 4 constrains to Top, Bottom, Left and Right Edges.
Now just select your image view and using the IB inspector select how
you would like your image: fill or fit as you can see as follow:
This is the updated answer of my previous one.
As the same approach of my previous answer, You can create an extension of UIView and add addBackground() method to it, as follows:
Remember: if you are adding it in a new .swift file, remember to add import UIKit
extension UIView {
func addBackground(imageName: String = "YOUR DEFAULT IMAGE NAME", contentMode: UIView.ContentMode = .scaleToFill) {
// setup the UIImageView
let backgroundImageView = UIImageView(frame: UIScreen.main.bounds)
backgroundImageView.image = UIImage(named: imageName)
backgroundImageView.contentMode = contentMode
backgroundImageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(backgroundImageView)
sendSubviewToBack(backgroundImageView)
// adding NSLayoutConstraints
let leadingConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 0.0)
let trailingConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let topConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0)
NSLayoutConstraint.activate([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
}
}
Note that the updates for this answer are:
Swift 4 code 🙂
Adding -programatically- NSLayoutConstraints: that's because when applying what's mentioned in my previous answer, it works fine for the current device orientation, but not when the application does support both portrait/landscape modes, if the device orientation has been changed, the background imageView size will be the same (same size) and not adapts the new width/height of the device screen, so adding constraints should solve this issue.
Adding default parameters: for more flexibility, you might -sometimes- want to change the default image or even the context mode for you background:
Usage:
Assuming that you want to call it in viewDidLoad():
override func viewDidLoad() {
//...
// you can call 4 versions of addBackground() method
// 1- this will add it with the default imageName and default contextMode
view.addBackground()
// 2- this will add it with the edited imageName and default contextMode
view.addBackground(imageName: "NEW IMAGE NAME")
// 3- this will add it with the default imageName and edited contextMode
view.addBackground(contentMode: .scaleAspectFit)
// 4- this will add it with the default imageName and edited contextMode
view.addBackground(imageName: "NEW IMAGE NAME", contextMode: .scaleAspectFit)
}
Here are your options for scaling!
For the .contentMode property:
ScaleToFill
This will scale the image inside the image view to fill the entire boundaries of the image view.
ScaleAspectFit
This will make sure the image inside the image view will have the right aspect ratio and fit inside the image view’s boundaries.
ScaleAspectFill
This will make sure the image inside the image view will have the right aspect ratio and fill the entire boundaries of the image view.
For this value to work properly, make sure that you have set the clipsToBounds property of the imageview to true.
class SecondViewController : UIViewController {
let backgroundImage = UIImage(named: "centralPark")
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.thirdChoiceField.delegate = self
self.datePicker.minimumDate = NSDate()
imageView = UIImageView(frame: view.bounds)
imageView.contentMode = .ScaleAspectFill
imageView.clipsToBounds = true
imageView.image = backgroundImage
imageView.center = view.center
view.addSubview(imageView)
self.view.sendSubviewToBack(imageView)
me showing my answer as which Lines of code helps me...
extension file code ....
extension UIView {
func addBackground() {
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
imageViewBackground.image = UIImage(named: "Your Background Image Name")
imageViewBackground.contentMode = .scaleAspectFill
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}
}
and in view controller file....
override func viewDidLoad() {
super.viewDidLoad()
view.addBackground()
}
Thank You Guys !
Ahmad Fayyas Solution in Swift 3.0:
func addBackground() {
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x:0, y:0, width: width, height: height))
imageViewBackground.image = UIImage(named: "YOUR IMAGE NAME GOES HERE")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.scaleAspectFill
self.view.addSubview(imageViewBackground)
self.view.sendSubview(toBack: imageViewBackground)
}
This uses PureLayout. You could just use AutoLayout with a few more lines.
UIImageView* imgView = UIImageView(image: myUIImage)
imgView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(imgView)
self.view.addConstraints(imgView.autoPinEdgesToSuperviewEdgesWithInsets(UIEdgeInsetsMake(0,0,0,0))
I used constraints to make the image "autoLayout". I made a view to show an activity indicator (with full background image), while the view on segue is loading. The code is as follows.
var containerView: UIView = UIView()
var actionIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
private func showActivityIndicator() {
///first I set the containerView and the background image
containerView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(containerView)
adjustConstFullSize(containerView, parentView: self.view)
let backImage = UIImageView(image: UIImage(named: "AppBackImage"))
backImage.contentMode = UIViewContentMode.ScaleAspectFill
backImage.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(backImage)
adjustConstFullSize(backImage, parentView: containerView)
////setting the spinner (activity indicator)
actionIndicator.frame = CGRectMake(0.0, 0.0, 40.0, 40.0)
actionIndicator.center = CGPointMake(containerView.bounds.size.width / 2, containerView.bounds.size.height / 2)
actionIndicator.hidesWhenStopped = true
actionIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
containerView.insertSubview(actionIndicator, aboveSubview: backImage)
///throw the container to the main view
view.addSubview(containerView)
actionIndicator.startAnimating()
}
This is the code for the "adjustConstFullSize" function.
func adjustConstFullSize(adjustedView: UIView!, parentView: UIView!) {
let topConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Top,
relatedBy: .Equal,
toItem: parentView,
attribute: .Top,
multiplier: 1.0,
constant: 0.0)
let leftConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Leading,
relatedBy: .Equal,
toItem: parentView,
attribute: .Leading,
multiplier: 1.0,
constant: 0.0)
let rightConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Trailing,
relatedBy: .Equal,
toItem: parentView,
attribute: .Trailing,
multiplier: 1.0,
constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Bottom,
relatedBy: .Equal,
toItem: parentView,
attribute: .Bottom,
multiplier: 1.0,
constant: 0.0)
parentView.addConstraints([topConstraint, leftConstraint, rightConstraint, bottomConstraint])
}
In the function shown above, I "tied" the containerView constraints to the main view constraints, making the view "full size". I did the same for the UIImageView and also set the contentMode to AspectFill - this is crucial, because we want the image to fill the content without stretching.
To remove the view, after the lazy loading, just use the code below.
private func hideActivityIndicator() {
actionIndicator.stopAnimating()
containerView.removeFromSuperview()
}
For this, I think you'll need to create a UIImageView that is pinned to the parent views top / bottom / left / right. This will make the UIImageView always the match the size of the display. Just make sure you set the content mode on the imageview to be AspectFit
var newImgThumb : UIImageView
newImgThumb = UIImageView(view.bounds)
newImgThumb.contentMode = .ScaleAspectFit
view.addSubview(newImgThumb)
//Don't forget this line
newImgThumb.setTranslatesAutoresizingMaskIntoConstraints(false)
NSDictionary *views =NSDictionaryOfVariableBindings(newImgThumb);
// imageview fills the width of its superview
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[newImgThumb]|" options:0 metrics:metrics views:views]];
// imageview fills the height of its superview
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[newImgThumb]|" options:0 metrics:metrics views:views]];
`
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
_imgBackGround.frame = CGRectMake(0, 0, screenWidth, screenHeight);`

Resources