I have an Application that is not using storyboard, but uses traditional xibs. And when I was adapting it to iPhone6/+ I noticed that when I add views from one view controller as a subview of view from another viewController it is resized but it shouldn't.
I have created test app to reproduce this issue so here is some code:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window!.rootViewController = ViewController(nibName:"ViewController", bundle : NSBundle.mainBundle())
self.window?.makeKeyAndVisible();
return true
}
class SubViewController: UIViewController
{
override func viewDidLoad()
{
self.view.frame = UIScreen.mainScreen().bounds
println("viewDidLoad view. frame\(self.view.frame) screen bounds \(UIScreen.mainScreen().bounds)")
// => viewDidLoad view. frame(0.0,0.0,320.0,568.0) screen bounds (0.0,0.0,320.0,568.0)
}
class ViewController: UIViewController
{
var subViewController : SubViewController?
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.subViewController = SubViewController(nibName:"SubViewController", bundle : NSBundle.mainBundle())
self.view.addSubview(self.subViewController!.view)
println("super view. frame\(self.view.frame) screen bounds \(UIScreen.mainScreen().bounds)")
//=>super view. frame(0.0,0.0,600.0,600.0) screen bounds (0.0,0.0,320.0,568.0)
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I use XCode6 GM and iOS8 GM.
If I set frame in subViewController's viewWillAppear it sets Ok, but still I want to figure out what causes such behavior?
I have found the way to turn off that behavior I just had to set AutoresizingMask of mine subviews to None:
[self.view setAutoresizingMask:UIViewAutoresizingNone];
My suggestion that when I was adding my subview to another view in viewDidLoad it had wrong frame in that moment. So when proper frame for superview was set it applied autoresizing mask to it subviews that caused troubles.
Related
I'm trying to set a gradient to the background of my subclassed NavigationController. When I add a colour to the same code it works well but I can't seem to let my gradient show up. I created a subclass of a UIView that returns a CAGradientLayer as its background view.
Here is my subclassed UIView : (Note the colours are weird so I am sure its loading the right Gradient.
#IBDesignable
class GenericBackgrounView: UIView {
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
///The roundness for the corner
#IBInspectable var cornerRadius: CGFloat = 0.0 {
didSet{
setupGradient()
}
}
func setupGradient() {
//let gradientColors = [bgDarkColor.cgColor, bgDarkColor.blended(withFraction: 0.5, of: bgLightColor).cgColor, bgLightColor.cgColor]
let gradientColors = [UIColor.brown.cgColor, UIColor.red.blended(withFraction: 0.5, of: UIColor.cyan).cgColor, UIColor.yellow.cgColor]
gradientLayer.colors = gradientColors
gradientLayer.locations = ESDefault.backgroundGradientColorLocations
setNeedsDisplay()
}
var gradientLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
override func awakeFromNib() {
super.awakeFromNib()
setupGradient()
}
override func prepareForInterfaceBuilder() {
setupGradient()
}
}
And Here is my UINavigationController :
class GenericNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
let backView = GenericBackgrounView(frame: self.view.frame)
backView.bounds = self.view.bounds
self.view.backgroundColor = UIColor.clear
self.view.addSubview(backView)
self.view.sendSubview(toBack: backView)
// Do any additional setup after loading the view.
}
}
Also note that my GenericBackgroundView works fine when I use it for any views I add in the interface builder.
I have been at this to long. I think I will suggest to Apple to setup some kind of Theming API in both code and Interface Builder... and the ability to add gradients straight into Interface Builder...
Thanks for you help.
Instead of setting it up in awakeFromNib() , try calling it in viewDidLayoutSubviews(). Reason is that in viewDidLayoutSubviews() will have the correct frame of the view , while in awakeFromNib() you wouldn't know the right frame of the view.From Apple Documentation.
Alright, I've tinkered a bit and found some working code. I would still love to understand the reason why this works and not the way I had it before. I hate feeling it works by magic...
here is the working code : (Remember that my gradient is in form of CAGradientLayer and I have made some static variable that has defaults.
import UIKit
class GenericNavigationController: UINavigationController {
let backViewGradient = Default.testGradientCALayer
override func viewDidLoad() {
super.viewDidLoad()
setupBackground()
}
func setupBackground() {
backViewGradient.frame = self.view.frame
self.view.layer.insertSublayer(backViewGradient, at: 0)
}
override func prepareForInterfaceBuilder() {
setupBackground()
}
}
What I'm wondering is how come since all the UIControls that are subclassed from UIView don't all work the same. They should all have a view that is the background and we should all be able to either add a layer or a subview to them and be able to get my previous code to work or my latest code too which does not work with TableViewCells.
I will leave this question open because I would love to know the truth behind this. I don't think I can fully grasp Swift or Xcode if it behaves somewhat magically and inconsistent.
There seem to be a bunch of questions on this for old versions of Swift/Xcode, but for some reason it hasn't been working with the latest update. I created a NSVisualEffectView, blurryView, and added the subview to my main view:
class ViewController: NSViewController {
#IBOutlet weak var blurryView: NSVisualEffectView!
override func viewDidLoad() {
super.viewDidLoad()
//background styling
blurryView.wantsLayer = true
blurryView.blendingMode = NSVisualEffectBlendingMode.behindWindow
blurryView.material = NSVisualEffectMaterial.dark
blurryView.state = NSVisualEffectState.active
self.view.addSubview(blurryView, positioned: NSWindowOrderingMode.above, relativeTo: nil)
// Do any additional setup after loading the view.
}
...
}
But when I run it, there is no effect on the window. (when I set it to within window, and layer it on top of my other view, the blur works correctly, but I only want the window to blur.) I also tried doing the same thing in my App Delegate class, but I can't connect my window as an outlet, and therefore can't add the blurry view to the window. Here's what the code would look like:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
blurryView.wantsLayer = true
blurryView.blendingMode = NSVisualEffectBlendingMode.withinWindow
blurryView.material = NSVisualEffectMaterial.dark
blurryView.state = NSVisualEffectState.active
self.window.contentView?.addSubview(blurryView)
}
...
}
To get an idea if what I'm looking for: NSVisualEffectView Vibrancy
It works quite easy:
In Interface Builder drag a NSVisualEffectView directly as a subview of the main view of your scene.
In the Properties Inspector set Blending Mode to Behind Window
Add the rest of the views you need as subviews of the NSVisualEffectView
That's it, you're done
Here's an example:
Panel 1 View Controller is my blurred view, Background View is the first (non-blurred) view in my "real"view hierarchy.
Swift 5:
Simply add this to your viewWillAppear and it should work:
override func viewWillAppear() {
super.viewWillAppear()
//Adds transparency to the app
view.window?.isOpaque = false
view.window?.alphaValue = 0.98 //you can remove this line but it adds a nice effect to it
let blurView = NSVisualEffectView(frame: view.bounds)
blurView.blendingMode = .behindWindow
blurView.material = .fullScreenUI
blurView.state = .active
view.window?.contentView?.addSubview(blurView)
}
i have dragged a container view from storyboard and set it black in background colour. but it didn't change the background colour.
class ViewController: UIViewController {
#IBOutlet weak var container: UIView!
override func viewDidLoad() {
super.viewDidLoad()
container.layer.backgroundColor = UIColor.blackColor().CGColor
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
How did you set the background color?
A container view is actually just a normal view, that is linked to a viewController. This means you can set the background the same way as you would for any other NSView or UIView. This is all the code I needed to add to my NSViewController class (not the ViewController inside the container, just the ViewController for the window).
#IBOutlet weak var containerView: NSView!
override func viewDidLoad() {
//other code in your viewDidLoad
containerView.wantsLayer = true
}
override func awakeFromNib() {
super.awakeFromNib()
containerView.layer?.backgroundColor = NSColor.black.cgColor
}
make sure to connect the IBOutlet if you have not already.
If you set the layers background color in viewDidLoad, the layer may not exist yet, (I don't know why). Accessing the layer in awakeFromNib has always worked for me, while accessing it in the viewDidLoad can be unreliable.
If you are working on IOS, most of this is not applicable, and this should be all you need
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
containerView.layer?.backgroundColor = UIColor.black.cgColor
}
I work less on IOS, so I have not ran into any issues with that, but that could be from lack of attempts. On OSX setting the background color in viewDidLoad will work about 50% of the time so there might still be an issue that I have not ran into.
If neither works, try unwrapping the layer rather than leaving it an optional (replacing the ? with a !) this will at least crash your program and probably tell you that layer is nil, if this is the case you should be figuring out why the layer is nil.
Also if the ViewController connected to the container view is a custom class, you don't have to bother with the IBOutlets, just call the view "view" in that custom class.
Sorry this got a bit long, but hope this helped
For me it make sense to add subviews in viewDidLoad, because it gets call when the view load, but i can't do that with my subviews. I have subviews which frame depends on another already added subviews height and width, and height and width is not correct before viewDidAppear. So i add my subviews in viewDidAppear, but viewDidAppear gets call everytime the view appears which is bad. So for my solution right now i add my subviews in viewDidAppear and when view disappear I remove the subviews in viewDidDisappear. Is there another way to archieve this without I manual removing subviews from superview.
Example
override func viewDidAppear(animated: Bool) {
print(PercentageView.frame.width)
print(PercentageView.frame.height)
CreateInfoRecipeLabels()//Add subviews
CreateCircleDiagram() //Add subviews
}
override func viewDidDisappear(animated: Bool) {
//When view disappear deallocate subviews
for view in PercentageView.subviews {
view.removeFromSuperview()
}
}
UPDATE - Tried this instead, but it is like it can't add subview before there is a frame?
#IBOutlet weak var RecipeInfoContain: UIView!
var MinLabel: SMIconLabel?
var Min = "15"
override func viewDidLoad() {
super.viewDidLoad()
RecipeInfoContain.addSubview(MinLabel!)
// Do any additional setup after loading the view.
}
override func viewWillLayoutSubviews() {
MinLabel = SMIconLabel(frame: CGRectMake(RecipeInfoContain.frame.width/4 - 50, RecipeInfoContain.frame.height/2 - 10, 100, 20))
MinLabel!.text = Min + " min"
//MinLabel.backgroundColor = UIColor.redColor()
MinLabel!.font = UIFont(name: "OpenSans", size: 11)
MinLabel!.textColor = UIColor.whiteColor()
MinLabel!.icon = UIImage(named: "Clock")
MinLabel!.clipsToBounds = true
MinLabel!.iconPadding = 5
MinLabel!.iconPosition = .Left
MinLabel!.textAlignment = .Left
}
Two thoughts:
If you use autolayout to dictate the frame of the subviews, then you can add them in viewDidLoad and the autolayout engine will take care of sizing them appropriately.
Just set translatesAutoresizingMaskIntoConstraints for the subviews to false and then add the appropriate constraints rather than setting frame values manually.
If you really want to manually adjust the frames, you can add the subviews in viewDidLoad, but then adjust the frames in viewWillLayoutSubviews.
So I created a scroll view and put an image view in it. I initialized the scroll view as a variable and wrote the following code in my ViewController and yet, the image won't scroll.
What's wrong?
Later on I'm also going to need to put several buttons into this scroll view so if I'm missing something here, it'd be great if you could address it for buttons as well.
My code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
ScrollView.scrollEnabled = true
}
#IBOutlet weak var ScrollView: UIScrollView!
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Try explicitly setting the content size like so:
self.scrollView.contentSize = CGSize(width:somethingBigger, height: somethingBigger);
To answer your follow-up question...Make height of the scrollView equal to that of the screen. Then it won't scroll vertically. Programmatic stuff overwrites storyboard stuff.