Swift scrollview adding from XIB not scrollable at all - ios

I'm trying to import from Xib full screen ScrollView into my ViewController.
Following that guide i've made many working examples of it, but when importing it from Xib, ScrollView not responding on scroll (not even bouncing)
My Xib View class:
class TestScroll: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.translatesAutoresizingMaskIntoConstraints = false
}
public static func getViewFromNib() -> TestScroll {
return UINib(nibName: "TestScroll", bundle: .main).instantiate(withOwner: nil, options: nil).first as! TestScroll
}
}
And this is how i adding it in ViewController:
override func viewDidLoad() {
super.viewDidLoad()
let testScroll = TestScroll.getViewFromNib()
self.view.addSubview(testScroll)
}
Please help, i've checked many guides already, but not found working example with Xib.

You need to set a frame / constraints
let testScroll = TestScroll.getViewFromNib()
testScroll.frame = self.view.bounds
self.view.addSubview(testScroll)
leave this
self.translatesAutoresizingMaskIntoConstraints = false
only if you'll set constraints

Related

How to load a xib file as a custom class?

I'm trying to use a xib file to create a reusable component (multiple times in the same View), and everything was fine until i wanted to update some controls from an #IBInspectable property. I found that #IBOutlet's are not set at that moment, so i did a search and found something
http://justabeech.com/2014/07/27/xcode-6-live-rendering-from-nib/
He is saving a the loaded view into proxyView so you could use it in the #IBInspectable. Unfortunately that code is a bit old and doesn't work as is. But my problem is when i try to load the nib as a class it doesn't work. It only works if i load it as UIView.
This line fails as this
return bundle.loadNibNamed("test", owner: nil, options: nil)?[0] as? ValidationTextField
It only works when is like this
return bundle.loadNibNamed("test", owner: nil, options: nil)?[0] as? UIView
I think that the problem is, the xib file's owner is marked as ValidationTextField, but the main view it's UIView. So when you load the nib it brings that UIView, that obviously has no custom properties or outlets.
Many examples about loading xib files say that the class must be in the file owner. So i don't know how to get the custom class using that.
import UIKit
#IBDesignable class ValidationTextField: UIView {
#IBOutlet var lblError: UILabel!
#IBOutlet var txtField: XTextField!
#IBOutlet var imgWarning: UIImageView!
private var proxyView: ValidationTextField?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder: NSCoder) {
super.init(coder: coder)!
}
override func awakeFromNib() {
super.awakeFromNib()
xibSetup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
xibSetup()
self.proxyView?.prepareForInterfaceBuilder()
}
func xibSetup() {
guard let view = loadNib() else { return }
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
// Saving the view in a variable
self.proxyView = view
}
func loadNib() -> ValidationTextField? {
let bundle = Bundle(for: type(of: self))
return bundle.loadNibNamed("test", owner: nil, options: nil)?[0] as? ValidationTextField
}
#IBInspectable var fontSize: CGFloat = 14.0 {
didSet {
let font = UIFont(name: "System", size: self.fontSize)
self.proxyView!.txtField.font = font
self.proxyView!.lblError.font = font
}
}
}
I don't even know if the rest will work. If what the link says it's true, getting the view after loading the nib will let me access to the outlets.
Running that code fails at this line
guard let view = loadNib() else { return }
I guess that it can't convert the UIView to the class so it returns nil, and then exits.
My goal is to have a reusable component that can be placed many times in a single controller. And be able to see its design in the storyboard.
Move xibSetup() to your initializers. awakeFromNib is called too late and it won't be called if the view is created programatically. There is no need to call it in prepareForInterfaceBuilder.
In short, this can be generalized to:
open class NibLoadableView: UIView {
public override init(frame: CGRect) {
super.init(frame: frame)
loadNibContentView()
commonInit()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
loadNibContentView()
commonInit()
}
public func commonInit() {
// to be overriden
}
}
public extension UIView {
// #objc makes it possible to override the property
#objc
var nibBundle: Bundle {
return Bundle(for: type(of: self))
}
// #objc makes it possible to override the property
#objc
var nibName: String {
return String(describing: type(of: self))
}
#discardableResult
func loadNibContentView() -> UIView? {
guard
// note that owner = self !!!
let views = nibBundle.loadNibNamed(nibName, owner: self, options: nil),
let contentView = views.first as? UIView
else {
return nil
}
addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = true
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.frame = self.bounds
return contentView
}
}
Note that the view that loads the nib must be the owner of the view.
Then your class will become:
#IBDesignable
class ValidationTextField: NibLoadableView {
#IBOutlet var lblError: UILabel!
#IBOutlet var txtField: XTextField!
#IBOutlet var imgWarning: UIImageView!
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
override func commonInit() {
super.commonInit()
updateFont()
}
#IBInspectable var fontSize: CGFloat = 14.0 {
didSet {
updateFont()
}
}
private func updateFont() {
let font = UIFont.systemFont(ofSize: fontSize)
txtField.font = font
lblError.font = font
}
}
I guess that the whole idea about a proxy object comes from misuse of the Nib owner. With a proxy object, the hierarchy would have to be something like this:
ValidationTextField
-> ValidationTextField (root view of the nib)
-> txtField, lblError, imgWarning
which does not make much sense. What we really want is:
ValidationTextField (nib owner)
-> UIView (root view of the nib)
-> txtField, lblError, imgWarning

Auto resize the UIView based on UILabel's content through Auto Layout

I have a requirement on showing a banner view to show a message,
Now the message's content may vary and the view should also resize depending on UILabels content.
UILabel is set to Word Wrap and numberOfLines is set to 0
The design in xib is,
And the respective class file is,
import UIKit
class ORABannerView: UIView {
#IBOutlet weak var bannerText: UILabel!
static func instantiate(message: String) -> ORABannerView {
let view: ORABannerView = initFromNib() ?? ORABannerView()
view.bannerText.text = message
return view
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
}
}
The initFromNib is an implemented as UIView's extension,
extension UIView {
class func initFromNib<T: UIView>() -> T? {
guard let view = Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as? T else {
return nil
}
return view
}
}
Tried with layoutIfNeeded() on the view, but it's not working for me.
Any suggestions will be appreciated.
You may want to try the following code,
Create a method sizeHeaderToFit and which returns height of the UIView based on its content's height.
private func sizeHeaderToFit(headerView: UIView?) -> CGFloat {
guard let headerView = headerView else {
return 0.0
}
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
return height
}
Then you can call the above method from the respective code,
sizeHeaderToFit(headerView: yourView)
In ORABannerView class, add override layoutSubviews,
override func layoutSubviews() {
super.layoutSubviews()
bannerText.preferredMaxLayoutWidth = bannerText.bounds.width
}
And in xib file, keep the constraints to its superview rather than safe area for code optimisation. [Optional]
Hope it helps.
Remove trailing OR leading constraints
set the size to fit property programmatically
I expect the class to look like
class ORABannerView: UIView {
#IBOutlet weak var bannerText: UILabel!
static func instantiate(message: String) -> ORABannerView {
let view: ORABannerView = initFromNib()
view.bannerText.text = message
return view
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
}
}
let v = ORABannerView.instantiate(message: "djhhejjhsdjhdjhjhdshdsjhdshjdhjdhjdhjddhjhjdshjdhdjshjshjdshjdshjdshjdshjdsjhdshjds")
self.view.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: self.view.topAnchor,constant:50),
v.leadingAnchor.constraint(equalTo: self.view.leadingAnchor,constant:0),
v.trailingAnchor.constraint(equalTo: self.view.trailingAnchor,constant:0)
])
New users autolayout, always forgot about two useful constraints:
func setContentCompressionResistancePriority(UILayoutPriority, for: NSLayoutConstraint.Axis)
func setContentHuggingPriority(UILayoutPriority, for: NSLayoutConstraint.Axis)
It also exists in IB too:
Try to play with the priority parameter

Changing variables of a custom UIView in another custom ViewController

I know it may be the basic question but I am new to Swift.
Also, I have tried various solutions on SO but could not resolve the issue.
So if anyone can help me with my problem.
I have a custom UIVIEW class as follows:
class SearchTextFieldView: UIView, UITextFieldDelegate{
public var searchText = UITextField()
override init(frame: CGRect) {
super.init(frame: frame)
initializeUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeUI()
}
func initializeUI() {
searchText.placeholder = "Enter model no"
searchText.backgroundColor = UIColor.white
searchText.textColor = UIColor.darkGray
searchText.layer.cornerRadius = 5.0
searchText.delegate=self
self.addSubview(searchText)
}
override func layoutSubviews() {
searchText.frame = CGRect(x: 20.0, y: 5.0, width: self.frame.size.width - 40,height : self.frame.size.height - 10)
}
}
Now I want to set text to SearchText textfield from another class which is as follows:
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
// Do any additional setup after loading the view.
}
func setupUI() {
let searchTextFieldView = SearchTextFieldView()
self.view.addSubview(searchTextFieldView) //adding view containing search box view at the top
**searchTextFieldView.searchText.text = "My Text"**
}
I am using Storyboard. Also, I can see the textfield with placeholder text.only problem is I can not set text to it.
Can anybody help. Whats wrong in my code.
It is needed to call searchTextFieldView.setNeedsDisplay(), this will in turn call override func draw(_ rect: CGRect) in class SearchTextFieldView.
Add override func draw(_ rect: CGRect) {} in SearchTextFieldView, and try setting searchText.text = <someValue> in draw(). You can use a String property in SearchTextFieldView, to get <someValue> from the client (one who is using SearchTextFieldView) class.
You are creating you view via SearchTextFieldView(), while you have 2 available initializers init(frame:) and init?(coder:).
If you change
let searchTextFieldView = SearchTextFieldView()
with
let searchTextFieldView = SearchTextFieldView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
you will see the text.
You are not setting frame to the view. Also you are not loading the .xib in the view class. It should be like:-
class SearchTextFieldView: UIView, UITextFieldDelegate{
//MARK:- Initializer
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize(withFrame: self.bounds)
}
override init(frame : CGRect) {
super.init(frame: frame)
initialize(withFrame: frame)
}
//MARK: - View Initializers
func initialize(withFrame frame : CGRect) {
Bundle.main.loadNibNamed("SearchTextFieldView", owner: self, options: nil)
view.frame = frame
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(view)
initializeUI()
}
}
Now you can call the below code in view controller:-
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
// Do any additional setup after loading the view.
}
func setupUI() {
let searchTextFieldView = SearchTextFieldView(frame: ?*self.view.bounds)
self.view.addSubview(searchTextFieldView)
//adding view containing search box view at the top
searchTextFieldView.searchText.text = "My Text"
}
Don't forget to create an xib with name "SearchTextFieldView.xib" as you are loading that nib in your initialize function.
Hope it helps :)
add frame for the searchTextFieldView inside setupUI() method. because the View got loaded on the view but its doesn't have a frame (x,y position, width and height). Change your UIViewController's colour to grey and u can see the your view loaded on the left corner (0,0). set frame size for the view that will solve this problem.

Swift UIGestureRecognizer not working when added in initWithCoder

I have a custom UIView subclass with UI designed in xib.
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
loadView()
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadView()
commonInit()
}
func commonInit() {
let rightSwipeRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("swipe"))
rightSwipeRecognizer.direction = .Right
dayInfoView.addGestureRecognizer(rightSwipeRecognizer)
}
func swipe() {
println("swiped")
}
private func loadView() {
let mainBundle = NSBundle.mainBundle()
let views = mainBundle.loadNibNamed("MyView", owner: self, options: nil)
let view = views.first as? UIView
addSubview(view!)
}
}
In my storyboard, I've added a view to VC and ensured that user interaction is enabled on that view. The result: no swipe handling
In case I add my view manually everything works fine:
override func viewDidLoad() {
super.viewDidLoad()
let myView = MyViewframe: CGRectMake(0, 300, 320, 200))
view.addSubview(myView)
}
What am I doing wrong?

IBOutlet properties nil after custom view loaded from xib

Something strange going on with IBOutlets.
In code I've try to access to this properties, but they are nil. Code:
class CustomKeyboard: UIView {
#IBOutlet var aButt: UIButton!
#IBOutlet var oButt: UIButton!
class func keyboard() -> UIView {
let nib = UINib(nibName: "CustomKeyboard", bundle: nil)
return nib.instantiateWithOwner(self, options: nil).first as UIView
}
override init() {
super.init()
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
// MARK: - Private
private func commonInit() {
println(aButt)
// aButt is nil
aButt = self.viewWithTag(1) as UIButton
println(aButt)
// aButt is not nil
}
}
That's expected, because the IBOutlet(s) are not assigned by the time the initializer is called.
Instead of calling commonInit() in init(coder:), do that in an override of awakeFromNib as follows:
// ...
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
// ...
Assuming you tried the standard troubleshooting steps for connecting IBOutlets, try this:
Apparently, you need to disable awake from nib in certain runtime cases.
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
guard subviews.isEmpty else { return self }
return Bundle.main.loadNibNamed("MainNavbar", owner: nil, options: nil)?.first
}
Your nib may not be connected. My solution is quite simple. Somewhere in your project (I create a class called UIViewExtension.swift), add an extension of UIView with this handy connectNibUI method.
extension UIView {
func connectNibUI() {
let nib = UINib(nibName: String(describing: type(of: self)), bundle: nil).instantiate(withOwner: self, options: nil)
let nibView = nib.first as! UIView
nibView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(nibView)
//I am using SnapKit cocoapod for this method, to update constraints. You can use NSLayoutConstraints if you prefer.
nibView.snp.makeConstraints { (make) -> Void in
make.edges.equalTo(self)
}
}
}
Now you can call this method on any view, in your init method, do this:
override init(frame: CGRect) {
super.init(frame: frame)
connectNibUI()
}
Building on #ScottyBlades, I made this subclass:
class UIViewXib: UIView {
// I'm finding this necessary when I name a Xib-based UIView in IB. Otherwise, the IBOutlets are not loaded in awakeFromNib.
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
guard subviews.isEmpty else { return self }
return Bundle.main.loadNibNamed(typeName(self), owner: nil, options: nil)?.first
}
}
func typeName(_ some: Any) -> String {
return (some is Any.Type) ? "\(some)" : "\(type(of: some))"
}
There is possibility that you not mentioned the FileOwner for xib.
Mention its class in File owner not in views Identity Inspector .
And how did you initiate your view from the controlller? Like this:
var view = CustomKeyboard.keyboard()
self.view.addSubview(view)

Resources