Swift - Add Custom Xib View As Subview Programmatically - ios

Ive made a custom xib that I've used in my storyboard before and i want simply create an instance of the custom view adjust size and then add it as a subview to a uiscrollview. Ive tried using this block of code in the viewdidload func of my view controller
let cardView = CardView(coder: NSCoder())
cardView!.frame.size.width = 100
cardView!.frame.size.height = 100
scrollView.addSubview(cardView!)
but I'm getting this error
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -containsValueForKey: cannot be sent to an abstract object
of class NSCoder: Create a concrete instance!'
EDIT:
this is the code for the swift file connected to CardView.xib
import UIKit
class CardView: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var cornerView: UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("CardView", owner: self, options: nil)
self.addSubview(view)
view.frame = self.bounds
cornerView.layer.cornerRadius = 3
cornerView.layer.masksToBounds = true
view.layer.shadowOffset = CGSizeMake(1, 5);
view.layer.shadowRadius = 2;
view.layer.shadowOpacity = 0.2;
view.layer.masksToBounds = false
}
}
instead of using auto layout i tried simply settings height and width to test adding subviews manually from these 2 lines(also just a heads up i am new to iOS development)
cardView!.frame.size.width = 100
cardView!.frame.size.height = 100

What i have used in case of using custom XIB for view initialization is below.
In the class of the view like for you its CardView the code goes like.
class CardView: UIView {
#IBOutlet weak var cornerView: UIView!
func setupWithSuperView(superView: UIView) {
self.frame.size.width = 100
self.frame.size.height = 100
superView.addSubview(self)
cornerView = UIView(frame: self.bounds)
cornerView.layer.cornerRadius = 3
cornerView.layer.masksToBounds = true
view.layer.shadowOffset = CGSizeMake(1, 5);
view.layer.shadowRadius = 2;
view.layer.shadowOpacity = 0.2;
view.layer.masksToBounds = false
}
}
and where you are calling this class for initialization, use this.
let cardView = NSBundle.mainBundle("CardView").loadNibNamed("", owner: nil, options: nil)[0] as! CardView
cardView.setupWithSuperView(scrollView)
Try this once. But make sure the first view of the xib file is of type CardView. I mean the class of the first view is CardView.

Related

ios: questions regarding init(frame:) and init?(coder:)

Apple's tutorial describes the difference between init(frame:) and init?(coder:) as
You typically create a view in one of two ways: by programatically
initializing the view, or by allowing the view to be loaded by the
storyboard. There’s a corresponding initializer for each approach:
init(frame:) for programatically initializing the view and
init?(coder:) for loading the view from the storyboard. You will need
to implement both of these methods in your custom control. While
designing the app, Interface Builder programatically instantiates the
view when you add it to the canvas. At runtime, your app loads the
view from the storyboard.
I feel so confused by the description "programtically initializing" and "loaded by the storyboard". Say I have a subclass of UIView called MyView, does "programtically initialization" mean I write code to add an instance of MyView to somewhere like:
override func viewDidLoad() {
super.viewDidLoad()
let myView = MyView() // init(frame:) get invoked here??
}
while init?(coder:) get called when in Main.storyboard I drag a UIView from object library and then in the identity inspector I set its class to MyView?
Besides, in my xcode project, these two methods end up with different layout for simulator and Main.storyboard with the same code:
import UIKit
#IBDesignable
class RecordView: UIView {
#IBInspectable
var borderColor: UIColor = UIColor.clear {
didSet {
self.layer.borderColor = borderColor.cgColor
}
}
#IBInspectable
var borderWidth: CGFloat = 20 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable
var cornerRadius: CGFloat = 100 {
didSet {
layer.cornerRadius = cornerRadius
}
}
private var fillView = UIView()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupFillView()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupFillView()
}
private func setupFillView() {
let radius = (self.cornerRadius - self.borderWidth) * 0.95
fillView.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: radius * 2, height: radius * 2))
fillView.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
fillView.layer.cornerRadius = radius
fillView.backgroundColor = UIColor.red
self.addSubview(fillView)
}
override func layoutSubviews() {
super.layoutSubviews()
}
func didClick() {
UIView.animate(withDuration: 1.0, animations: {
self.fillView.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
}) { (true) in
print()
}
}
}
Why do they behave differently? (I drag a UIView from object library and set its class to RecordView)
I feel so confused by the description "programtically initializing" and "loaded by the storyboard".
Object-based programming is about classes and instances. You need to make an instance of a class. With Xcode, there are two broadly different ways to get an instance of a class:
your code creates the instance
you load a nib (such a view controller's view in the storyboard) and the nib-loading process creates the instance and hands it to you
The initializer that is called in those two circumstances is different. If your code creates a UIView instance, the designated initializer which you must call is init(frame:). But if the nib creates the view, the designated initializer that the nib-loading process calls is init(coder:).
Therefore, if you have a UIView subclass and you want to override the initializer, you have to think about which initializer will be called (based on how the view instance will be created).
First your delineation between init?(coder:) and init(frame:) is basically correct. The former is used when instantiating a storyboard scene when you actually run the app, but the latter is used when you programmatically instantiate it with either let foo = RecordView() or let bar = RecordView(frame: ...). Also, init(frame:) is used when previewing #IBDesignable views in IB.
Second, regarding your problem, I'd suggest you remove the setting of the center of fillView (as well as the corner radius stuff) from setupFillView. The problem is that when init is called, you generally don't know what bounds will eventually be. You should set the center in layoutSubviews, which is called every time the view changes size.
class RecordView: UIView { // this is the black circle with a white border
private var fillView = UIView() // this is the inner red circle
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupFillView()
}
override init(frame: CGRect = .zero) {
super.init(frame: frame)
setupFillView()
}
private func setupFillView() {
fillView.backgroundColor = .red
self.addSubview(fillView)
}
override func layoutSubviews() {
super.layoutSubviews()
let radius = (cornerRadius - borderWidth) * 0.95 // these are not defined in this snippet, but I simply assume you omitted them for the sake of brevity?
fillView.frame = CGRect(origin: .zero, size: CGSize(width: radius * 2, height: radius * 2))
fillView.layer.cornerRadius = radius
fillView.center = CGPoint(x: bounds.midX, y: bounds.midY)
}
}

How to update constraints after adding UIView from xib on UIScrollView in swift?

I have a UIViewScroll(background color is blue) in view controller. I need a UIView(background color is white) that were from Xib. The Xib view has a UILabel(background color is green) with constraints. Now, the problem is UILabel constraints not applied after adding it to scrollView. How to add UIView without loss of constraints? Refer following screenshots and code.
Note:
I need to just update constraints of the sub views of the UIView without using IBOutlets of NSLayoutConstraints.
UIView on Xib:
Code:
class ViewController: UIViewController {
#IBOutlet var scrollView: UIScrollView!
var profileView:UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.profileView = UINib.init(nibName: "ProfileView", bundle: nil).instantiate(withOwner: self)[0] as! UIView
self.scrollView.addSubview(self.profileView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.profileView.layer.frame.size = CGSize(width: self.scrollView.frame.width, height: self.profileView.frame.height)
self.profileView.layer.position = CGPoint(x: self.scrollView.frame.width/2, y: (self.profileView.frame.height/2)+10)
}
}
Output:
Update: More Information
I am aware to set contentSize of the scrollView. I used layer properties of UIView for manipulating height and width of the UIView. Instead of changing height and width also I need to update constraints of the sub views of UIView.
This is an example for understanding. But, In real I will be add more views like that.
Github Repository :
https://github.com/RAJAMOHAN-S/ScrollViewTest
Required output:
Your best bet is to set the constraint of self.profileView programmatically, I've added an example below to get you started.
class TestVC: UIViewController {
#IBOutlet var scrollView: UIScrollView!
private var profileView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.profileView = UINib.init(nibName: "ProfileView", bundle: nil).instantiate(withOwner: self)[0] as! UIView
self.configureProfileView()
}
private func configureProfileView() -> Void {
self.profileView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.addSubview(self.profileView)
self.profileView.widthAnchor.constraint(equalTo: self.scrollView.widthAnchor).isActive = true
self.profileView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
// Pin the profile view to the top of the scrollView
self.profileView.topAnchor.constraint(equalTo: self.scrollView.topAnchor).isActive = true
}
}
More information can be found here too: https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html

How to stack custom views inside a UIStackView

Note: I'm pretty new working with iOS UI.
I want to create a custom view that stacks a custom view inside.
So I created the custom UIStackView
class CustomStackView: UIStackView {
func addItem(color:UIColor){
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "RowView", bundle: bundle)
let rowView = RowView();
let view = nib.instantiate(withOwner: rowView, options: nil).first as! UIView
rowView.addSubview(view)
rowView.view.backgroundColor = color;
addArrangedSubview(rowView)
}
}
class RowView :UIView{
#IBOutlet var view: UIView!
override public var intrinsicContentSize: CGSize {
return CGSize(width: view.frame.width,height:view.frame.height)
}
}
in the RowView.xib I created a simple layout for testing:
Simulated Metrics = Freeform
Height = 100
And the ViewController.swift:
class ViewController: UIViewController {
#IBOutlet weak var customStackView: CustomStackView!
#IBOutlet weak var constraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
customStackView.addItem(color: UIColor.red)
customStackView.addItem(color: UIColor.blue)
customStackView.addItem(color: UIColor.green)
}
#IBAction func click(_ sender: Any) {
constraint.constant = -customStackView.frame.height
UIView.animate(withDuration: 4, animations: {
self.view.layoutIfNeeded();
},completion:nil)
}
}
The result:
The first and second item are displayed correctly but the third is higher than expected.
In addition if I click the button (which should hide the Stackview) keep the "extra" height visible:
How can I fix that?
Edit: Tried the #KristijanDelivuk solution adding a trailing view. And didn't work. Adding cyan color to the view I got this result:
override func viewDidLoad() {
super.viewDidLoad()
customStackView.addItem(color: UIColor.red)
customStackView.addItem(color: UIColor.blue)
customStackView.addItem(color: UIColor.green)
let view = UIView();
view.heightAnchor.constraint(equalToConstant: 50).isActive = true;
view.backgroundColor = UIColor.cyan;
customStackView.addArrangedSubview(view)
}
You can try adding an empty UIView as your last element of UIStackView:
So your hierarchy should look something like this:
- STACKVIEW
-- 1ST ADDED CUSTOM VIEW
-- 2ND ADDED CUSTOM VIEW
-- 3RD ADDED CUSTOM VIEW
-- EMPTY UIVIEW
Empty UIView will take all unallocated space from 3rd view and all should be displayed correctly.
For repositioning button after hiding/showing stackview you can create for example "top constraint" and then on tap change top constraint height to (-) stackview.height or (+) stackview.height - This shouldn't be any problem.

Can't change attributes of button in view loaded from nib file

I am loading a view which I created inside the interface builder like this:
let contentView = Bundle.main.loadNibnamed("ContentView", owner: nil, options: nil)?.first as! ContentView
The view contains an UIImageView and a UIButton. Both of them are connected with IBOutlets to the ContentView class file.
Now when I am loading the view I am setting the UIImageView's image which works fine. I am also trying to change the UIButton's text and backgroundColor but both won't change. Interestingly the button doesn't keep the color I set in the interface builder but the Text.
The code I am using to change the text and the color:
contentView.contentButton.backgroundColor = .red
contentView.contentButton.titleLabel?.text = "someText"
Thanks for the help.
ContentView.swift
class ContentView: UIView {
#IBOutlet weak var mainImageView: UIImageView!
#IBOutlet weak var contentImageView: UIImageView!
#IBOutlet weak var contentButton: UIButton!
}
The code I am using to create the views:
ContentScrollViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<imageArray.count {
let xPosition = Int(self.view.frame.width) * i
let contentView: ContentView = Bundle.main.loadNibNamed("ContentView", owner: nil, options: nil)?.first as! ContentView
contentView.frame.origin = CGPoint(x: xPosition, y: -Int((self.navigationController?.navigationBar.frame.height)!))
contentView.mainImageView.contentMode = .scaleAspectFill
contentView.contentImageView.image = #imageLiteral(resourceName: "stoerer_png_neu")
//Not working
contentView.specialButton.backgroundColor = .red
if let url = URL(string: imageArray[i]) {
contentView.mainImageView.af_setImage(withURL: url)
}
mainScrollView.contentSize.width = mainScrollView.frame.width * CGFloat(i + 1)
mainScrollView.addSubview(contentView)
}
}
You have to use
contentView.contentButton.setTitle("someText", for: .normal)
and
contentView.contentButton.setTitleColor(.red, for: .normal)
because you need to set those attributes for a specific UIControlState
For the background you need to use the layer:
contentView.contentButton.layer.backgroundColor = UIColor.red.cgColor
After restarting all files connected to the problem I finally managed to find the issue. Somehow the constraints of the UIButton in .xib were responsible for the issue. So if you are experiencing the same please check your constraints. :]

Using View from Storyboard for ImagePicker

I'm trying to create an overlay on top of an ImagePicker, and finding I can create the UIView programmatically, but would prefer to use a UIView that I can manipulate within Storyboard.
As you can see from the code snipper below, I've assigned the UIImagePickerControllerDelegate to my ViewController, created the image picker, and have a method for assigning the overlay view that I want. If I try to reference a UIView that is an outlet tied to a UIView that exists in the storyboard, it doesn't appear. If If create it programmatically, all good. How can I assign it to the UIView that I want to drag-drop controls onto in Storyboard?
Thanks in advance for any advice/guidance.
Cheers,
Carl
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// UIView for telemetry data
#IBOutlet weak var telemetryPanel: UIView!
...
override func viewDidAppear(animated:bool) {
...
let cameraImagePicker = UIImagePickerController()
...
// Call method to create a custom overlay for the camera
cameraImagePicker.cameraOverlayView = self.customViewForImagePicker(cameraImagePicker)
}
func customViewForImagePicker(imagePicker: UIImagePickerController!) -> UIView {
// TODO: - Assigning the view to the telemetry panel does not work
// let view:UIView = self.telemetryPanel
// view.backgroundColor = UIColor.whiteColor()
// view.alpha = 0.25
// return view
// Creating the view programmatically overlays it
let cameraAspectRatio:CGFloat = 4.0 / 3.0;
let screenSize = UIScreen.mainScreen().bounds.size
let imageWidth = floorf(Float(screenSize.width) * Float(cameraAspectRatio))
let view: UIView = UIView(frame: CGRectMake(0, screenSize.height-65, CGFloat(imageWidth), 65))
view.backgroundColor = UIColor.whiteColor()
view.alpha = 0.25
return view
}
When initialising a view that has been created in interface builder, you will need to load it using NSBundle.mainBundle().loadNibNamed()
Check out this answer on how to do that.

Resources