UIView not resizing from xib file Swift - swift

I have created an xib file with a UIView inside of it. I am using this view inside of my storyboard, and would like it to resize based on the content within it. When I explicitly set the height of the view in the storyboard, it is the same height as the XIB file. Each phone screen seems to have the same xib height and width as well, which causes the view to go off the screen, no matter what I set the constraints to be within the storyboard.
I have tried this to hide the view and set the height of the banner to 35
self.specialistBanner.pillView.isHidden = true
specialistBannerHeight.constant = 35
I have tried
specialistBannerHeight.constant = 35
to get the view to be smaller within the storyboard
I have also tried
self.specialistBanner.sizeToFit()
with no avail
XIB File for the specialist banner view
Label inside the xib file
Pill View within the xib
Storyboard file using the xib view inside of a view controller
class SpecialistBannerView: UIView {
#IBOutlet weak var bannerTitle: UILabel!
#IBOutlet weak var pillView: PillCollectionView!
var viewModel = SpecialistBannerViewModel()
// Initialize xib
let SpecialistBannerView = "SpecialistBannerView"
override func awakeFromNib() {
super.awakeFromNib()
self.translatesAutoresizingMaskIntoConstraints = false
}
override init(frame: CGRect) {
super.init(frame: frame)
initXib(view: self)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initXib(view: self)
}
func initXib(view: SpecialistBannerView) {
let viewToShow = Bundle.main.loadNibNamed(SpecialistBannerView, owner: self, options: nil)?[0] as? UIView ?? UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.sizeToFit()
view.addSubview(viewToShow)
view.frame = view.bounds
view.pillView.setUp()
view.sizeToFit()
view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
viewModel.specialistBannerViewProtocol = self
}
Any help would be appreciated

I had your same problem. I found the answer in another question.
You have to override LayoutSubViews() in your custom view and set the bounds
override func layoutSubviews() {
super.layoutSubviews()
// we need to adjust the frame of the subview to no longer match the size used
// in the XIB file BUT the actual frame we got assinged from the superview
self.view.frame = self.bounds
}

Related

Strange cornerRadius behaviour on UIView from xib

I created a custom Numpad keyboard through xib and wanted to initialize it with a rounded corners.
Here is the code I use:
import UIKit
class NumpadView: UIView {
#IBOutlet weak var resetButton: NumpadButton!
#IBOutlet weak var decimalButton: NumpadButton!
var target: UITextInput?
var view: UIView?
init(target: UITextInput, view: UIView) {
super.init(frame: .zero)
self.target = target
self.view = view
initializeSubview()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initializeSubview()
}
func initializeSubview() {
let xibFileName = "NumpadView"
let view = Bundle.main.loadNibNamed(xibFileName, owner: self, options: nil)![0] as! UIView
self.layer.cornerRadius = 30
self.layer.masksToBounds = true
self.addSubview(view)
view.frame = self.bounds
self.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
But then I receive the strange view look in area where cornerRadius is implemented:
How to remove that grey background which is visible near the rounded corners?
UPDATE:
According to View Debugger it seems like this grey layer between yellow square and Visual Effect View is a UICompatibilityInputViewController:
How I presenting the Numpad:
1.I created a NumpadView as a UIView subclass in a xib:
2.In my VC I just change a standard textField.inputView property on my custom NumpadView:
import UIKit
class NumpadViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
setupKeyboardHide()
textField.delegate = self
textField.inputView = NumpadView(target: textField, view: view)
}
}
Test project on Github
Another option to get your "rounded corners"...
Get rid of the Visual Effect View in your Numpad class, and set a layer mask on the superview at run-time.
In class NumpadView: UIView, UIInputViewAudioFeedback:
override func layoutSubviews() {
super.layoutSubviews()
guard let sv = superview else { return }
let maskLayer = CAShapeLayer()
let bez = UIBezierPath(roundedRect: bounds, cornerRadius: 16)
maskLayer.path = bez.cgPath
sv.layer.mask = maskLayer
}
Looks like this:
Sure, the NumpadView object has rounded corner. However, you are using it like this:
textField.inputView = NumpadView(target: textField, view: view)
So, that part which is not rounded is the textField's inputView. I'm not quite sure if you can modify its layer to have corner radius. But, if you really want to get that rounded corner effect, an easier approach is just to add the NumpadView directly to the parent view and anchored to the bottom. Then show it via the begin editing delegate of the textfield (and hide via end editing).

Custom UIView retained in memory

I am having an annoying memory issue with a custom UIView when trying to add it as a tableView footer in a VC. Here is what I do:
I declare a custom UIView class called tableViewHeader. My footer is supposed to display a logo and some text, so it has 2 global variables (a UIImage View & a TextView) and one init method to initialize this UIView class (i.e., by adding the two subViews and set some constraints).
In my VC, in the TableView's footerForSection method I declare a new tableViewFooter variable, set the sub-views (attach my PNG, etc.)
and return that view.
The code works fine to display the footer but it wrecks havoc on my memory - the moment I run my app and open the VC that contains that tableViewHeader my memory jumps by ~20MB (~ 20% jump) and doesn't get released once my VC is released (I checked, the VC itself gets correctly de-initialized but it doesn't show any drop in memory).
I tested a few things:
Without adding that footerView and opening/ closing the VC doesn't show that 20MB jump, so I am fairly sure that this is caused by this custom tableViewHeader...
I also checked to make sure the UIImage I load from my Assets is not too large but it's only 200Kb. On top the problem is more the fact that the memory doesn't get released after my VC gets released...
I've tried declaring my variables (the tableViewFooter variable and the sub-views within that new class) but that leaves my variables as nil when I try to add them as sub-views...
I am running out of ideas as to what the culprit is/ how to fix it so any help you could give me would be really highly appreciated!
My code is below - thanks in advance!
Francois
My TableFooterView class:
class MessageTableFooterView: UIView {
var shouldUpdateConstraints = true
var logoView: UIImageView!
var explanationTextView: UITextView!
var addFriendBtn: UIButton!
override init(frame: CGRect){
super.init(frame: frame)
logoView = UIImageView(frame: CGRect.zero)
logoView?.autoSetDimension(.height, toSize: 145)
logoView?.autoSetDimension(.width, toSize: 145)
self.addSubview(logoView)
explanationTextView = UITextView(frame: CGRect.zero)
explanationTextView?.isEditable = false
explanationTextView?.isSelectable = false
explanationTextView?.font = UIFont(name: "HelveticaNeue", size: 17)
explanationTextView?.textColor = .darkGray
explanationTextView?.textAlignment = .center
self.addSubview(explanationTextView)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
My tableView viewForFooter Method:
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if MessageService.instance.savedMsgs.count > 0 {
return nil
} else {
let headerView = MessageTableFooterView(frame: CGRect.zero)
headerView.logoView.image = UIImage(named: "savedMessagesIcon")
headerView.explanationTextView?.text = "Blablabla"
return headerView
}
}

Avoid boilerplate code to setup UI in viewDidLoad

i am just wondering how did you properly setup your UI in your IOS developments with Swift. Generally, I feel like I need to put a lot of statements in viewDidLoad lifecycle method of a view controller to customize UI elements. I know that I can use storyboard to help to setup those UI elements but sometimes we need to make some adjustments programmatically. Those adjustments resulting in a huge and boilerplate code in viewDidLoad. So, how do you handle this ? Did your use extensions only for the UI part ? Specific classes ? How you can clearly separate UI from logic ?
Make a custom view for it!
If you find yourself writing a lot of this kind of code:
myView.someProperty1 = someValue1
myView.someProperty2 = someValue2
myView.someProperty3 = someValue3
myView.someProperty4 = someValue4
myView.someProperty5 = someValue5
myView.addSubView(subView1)
myView.addSubView(subView2)
myView.addSubView(subView3)
...
and the values that you give the properties are all independent of the view controller, it might be time to create a custom view.
Here is an example:
Create an xib file for your view, and name it the same name as your custom view. You will be adding the subviews of your custom view and all the constraints you need here.
And then you can do something like this:
#INDesignable // add this if you want to see your view drawn on the storyboard!
class MyCustomView: UIView {
#IBOutlet var subView1: UIImageView!
#IBOutlet var subView2: UITextField!
#IBOutlet var subView3: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
let view = viewFromNibForClass()
view.frame = bounds
view.autoresizingMask = [
UIViewAutoresizing.flexibleWidth,
UIViewAutoresizing.flexibleHeight
]
addSubview(view)
// set up your view here...
// set all the properties and stuff
}
private func viewFromNibForClass() -> UIView {
let bundle = Bundle(for: MyCustomView.self)
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
}

loadnibnamed not loading subviews

I am trying to load a xib filed called "AddProgramView" using loadnibnamed when a user presses a button. I created the xib in storyboard, placed various subviews within the xib view, and hooked up outlets to a corresponding swift file named AddProgramView. When I load the xib, none of the subviews appear. They are all missing. Help? Here's my code
Within my main view controller:
#IBAction func addProgram(sender: UIButton) {
if let _ = addProgramView {
addProgramView!.frame = CGRectMake(0, self.window!.frame.origin.y + self.window!.frame.height, self.window!.frame.width, CGFloat(325))
}
else {
let addProgramViews = NSBundle.mainBundle().loadNibNamed("AddProgramView", owner: self, options: nil)
addProgramView = addProgramViews.first as? AddProgramView
addProgramView!.frame = CGRectMake(0, self.window!.frame.origin.y + self.window!.frame.height, self.window!.frame.width, CGFloat(325))
window?.addSubview(addProgramView!)
}
window?.bringSubviewToFront(addProgramView!)
UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
self.addProgramView!.frame.origin.y -= CGFloat(325)
}, completion: nil)
}
The AddProgramView.swift file:
class AddProgramView: UIView {
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var cancelButton: UIButton!
#IBOutlet weak var instructionsLabel: UILabel!
}
First check, if you have your class name declared in the view itself, not in the file owner.
If not, do it. Then try it again.
Another option is to create IBOutlet in your AddProgramView.swift
#IBOutlet var contentView: UIView!
and remove the class name from the view and add it to File's Owner.
Connect contentView by right-clicking at the File's owner:
Then create init methods like this:
class AddProgramView: UIView {
#IBOutlet var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
NSBundle.mainBundle().loadNibNamed("AddProgramView", owner: self, options: nil)
self.addSubview(self.contentView)
self.contentView.frame = self.bounds
}
}
And now you can use the AddProgramView wherever you want. Either by init(frame:)
let addProgramView = AddProgramView.init(frame: CGRectMake(0, 0, 320, 30))
or you can use them directly inside storyboard by adding UIView and set them class to AddProgramView (No extra line of code, storyboard will call init(coder:) for you)
I've created example project for you - CustomView project
Double-check that you don't have a size class specified in your xib file. It should say Any/Any at the bottom of the editor window.

Swift: Reusable UIView in storyboard and sizing constraints

I'm trying to create a reusable UIView in Swift that I can plug into my Storyboard view controllers. My key issue right now is that the reusable UIView "widget" doesn't fully fit into the UIView box in the storyboard. I followed this tutorial to set up the reusable UIView widget
Created a subclass of UIView and a corresponding .xib -- and connected these:
import UIKit
class MyWidgetView: UIView {
#IBOutlet var view: UIView!;
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
NSBundle.mainBundle().loadNibNamed("MyWidgetView", owner: self, options: nil);
self.addSubview(self.view);
}
}
In the XIB, which is the interface file corresponding to the code above, I used UIView with Freeform size under the Simulated Metrics, and Scale to Fill under View mode.
In the main storyboard, I added a UIView block (same rectangular shape) and changed the Class to MyWidgetView
It works, but the components I created in the XIB look squished in the actual app, despite the fact that I used layout constraints in both the XIB and also the main storyboard.
See the screenshot. The pink part isn't supposed to appear, since that is just a color of the UIVIew on the main storyboard that I added to test the sizing. That UIView is actually MyWidgetView (after I changed the class in step 3. So in theory, since MyWidgetView == the UIView on the main storyboard, and that UIView has constraints that make it rectangular in the superview, then why is my widget squished? The blue part below should extend all the way right.
The actual view hierarchy loaded from the nib file in your code is added via
self.addSubview(self.view). So, the frame of your self.view actually has no relationship with its parent, i.e. MyWidgetView.
You may choose either adding layout constraints through code or just setting its frame after being added as a subview. Personally, I prefer the latter. In my experiment, the following is what works for me. I am using Xcode 6.4, which I think is not the same one as yours.
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if let nibsView = NSBundle.mainBundle().loadNibNamed("MyWidgetView", owner: self, options: nil) as? [UIView] {
let nibRoot = nibsView[0]
self.addSubview(nibRoot)
nibRoot.frame = self.bounds
}
}
Alternatively the variable frame can be overridden. This code worked for me when CardImgText was set to files owner for the view.
class CardImgTxt: NSView {
#IBOutlet var view: NSView!
override var frame: NSRect{
didSet{
view.frame = bounds
}
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
// Drawing code here.
}
required init?(coder: NSCoder) {
super.init(coder: coder)
NSBundle.mainBundle().loadNibNamed("View", owner: self, topLevelObjects: nil)
addSubview(view)
}
}
if you are more interested in efficiency than real time updating. Then replace :
override var frame: NSRect{
didSet{
view.frame = bounds
}
}
with:
override func viewDidEndLiveResize() {
view.frame = bounds
}