I've asked a simular question before with no working result yet, but it might have too much "stuff" which is irrelevant to the exact issue I'm experiancing:
I used to be an Android-only developer in my current position and did not do any iOS apps before this so I might be troubling myself with the wrong stuff.
I want to load an view from xib which has a parent class I'd love to be able to do something like this (e.g. in UIViewController):
override func viewDidLoad() {
let view = MyView()
view.unlimitedLabel.text = "bla bla unlimited character string"
self.view.addSubView(view)
}
The MyView would be like this (I'm omitting what I added just to keep it clean):
import UIKit
class MyView : UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var deadlineLabel: UILabel!
}
So what init's are recommended, what should I apply to the view to make it respect the autoLayout? I understand it will be hard to do the AutoLayout when the view doesn't know it's parent and such.
for now I'm usually overriding all init's and call a private func like
private func setup() {
NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)
view.setTranslatesAutoresizingMaskIntoConstraints(false)
addSubview(view)
}
This barely ever works as expected, I have it working for height sometimes, but then the width is not being respected it just takes the xib's width. In general the view will have the xib's in UIBuilder size.
The xib is set correctly it's working fine for tableViewCell's right now. And the "MyView" class is set to the xib's file owner, outlets linked.
Note: I'm on iOS7 using Swift
Related
Let's say I have SomeViewController: UIViewController, and I have a custom view CustomView: UIView, defined as a XIB, that I want to display. This custom view will be reused in other view controllers and even multiple times in the same view controller.
class CustomView: UIView {
#IBOutlet public var label: UILabel!
}
The way I have always added this view has been:
class UIExamples: UIViewController {
#IBOutlet private var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Assume makeViewFromNib returns the view [0] in the Nib.
let customView = makeViewFromNib(nib: "\(CustomView.self)", owner: self) as! CustomView
customView.frame = myView.bounds
myView.addSubview(customView)
}
}
Let's say that later on I want to modify something about the CustomView via a public property label.
I could do it inside viewDidLoad ONLY BECAUSE I have access to customView, but what if I want to change it in some other function? What I have seen is that one would have to do
let customView = myView.subviews[0] as! CustomView
customView.label.text = "some text"
which does not look right.
So, I thought the right way should be this:
class UIExamples: UIViewController {
#IBOutlet public var customView: CustomView! // Now this is always a CustomView type
override func viewDidLoad() {
super.viewDidLoad()
// Assume makeViewFromNib returns the view [0] in the Nib.
customView = makeViewFromNib(nib: "\(CustomView.self)", owner: self) as! CustomView
customView.label.text = "some text" // DOES NOT WORK!
}
}
That last line customView.label.text does not work. In fact, the label is not even seen on the screen. What am I doing wrong?
OK, didn't read (or maybe was reading before edit) that you use xib. If ViewController is created from xib with label in it this will be correct way:
set myView class in xib here:
and then connect IBOutlet (remove current one from xib here:
and then from code).
Now myView.label.text = "some text" should work without further issues.
Good luck!
If you create your view from code do it in this manner:
class UIExamples: UIViewController {
#IBOutlet private var myView: CustomView!
override func viewDidLoad() {
super.viewDidLoad()
// Assume makeViewFromNib returns the view [0] in the Nib.
myView = makeViewFromNib(nib: "\(CustomView.self)", owner: self) as! CustomView
myView.frame = view.bounds
view.addSubview(myView)
}
}
Because you already have property storing this view in your view controller it's unnecessary to dig inside subviews, it will work like that
myView.label.text = "some text"
And reason for
customView = makeViewFromNib(nib: "\(CustomView.self)", owner: self) as! CustomView
customView.label.text = "some text"
isn't working is because it's completely new view that wasn't added to your view controller subviews (also frame wasn't set BTW). And because you changed value of your customView property it's now not pointing to old instance of view, that is present in subviews (you can still see that "old one" but not change it).
But I really recommend to use pointer created once, as correct class to avoid casting. (Or creating view directly in xib / storyboard, otherwise #IBOutlet is not necessary)
Posting my own answer.
Create the XIB file.
Create the UIView subclass Swift file.
Under the XIB file owner's Identify Inspector custom class field, type in the UIView subclass name (your custom view).
Under the XIB file owner's Connections Inspector, make sure all IBOutlets in the Swift file are connected.
Add a view to the view controller and under its Identify Inspector custom class type, specify the custom class name.
Important:
* In your XIB swift file, you have to properly load the XIB content view.
...
/// Initializer used by Interface Builder.
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
/// Initializer used programmatically.
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
...
func configure() {
let contentView = // here use many of the functions available on the internet to
// load a view from a nib.
// Then add this view to the view hierarchy.
addSubview(contentView)
}
Using swift 3, I have a UIScrollView in my main view. I call it my "feature scroll view"
I have also added a custom class to my UIScrollView, which has one function in it called loadFeatures()
featureScrollView.swift:
class featureScrollView: UIScrollView {
public func loadFeatures () {
//Add Pictures to the UIScrollView
}
How do i call loadFeatures() on the UIScrollView instance that exists from my ViewController.swift class in my main view?
In my ViewController.swift I want to simply write
scrollView.loadFeatures()
and have the code in loadFeatures() apply to the scrollView that is currently on the screen. I am beginning learning Swift and I appreciate everybody's help greatly!
image of ScrollView and corresponding class
Bind the IBOutlet of scrollview (Scrollview that you have shown in https://i.stack.imgur.com/5HRH0.png) in your ViewController.swift and just call loadFeatures() from ViewController.swift with that outlet instace.
ex., once you bind outlet like as below
class ViewController: UIViewController {
#IBOutlet weak var scrollView: featureScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.loadFeatures()
}
}
Since you've already made a custom class for your ScrollView you could easily access that by subclassing your scrollView in your *.storyboard
For example:
You could set the custom class of your scrollView by placing it in here
Which would look like this
Note: This is just one way of doing things
Another is to do it manually inside your UIViewController class like this
class ViewController: UIViewController {
var ftScrollView: FeatureScrollView!
override func viewDidLoad() {
super.viewDidLoad()
ftScrollView = FeatureScrollView() // I Initialized the scrollView
ftScrollView.frame = // some frame here
view.addSubView(ftScrollView)
ftScrollView.loadFeatures()
}
}
PS: Please conform to swift coding standards. You should always start class names with a capital letter, thus this class featureScrollView: UIScrollView should be named like this class FeatureScrollView: UIScrollView
I'm trying to set up a view inside a container view. It has to be done this way due to several different controllers for the views inside the swipeView.
#IBOutlet weak var containerView: ContainerView!
override func viewDidLoad() {
super.viewDidLoad()
//Some other stuff
//Create new swipeView
var swipeView = MDCSwipeToChooseView(frame: containerView.frame, options: options)
//Add the view from the controller to the swipeView
swipeView.addSubview(containerViewController.view)
//Add the swipeView to the main view
self.view.addSubview(swipeView)
I end up with this
The white area is the view that should inherit containerView's size. The containerView is the pink one in the background and it's shown properly. I have noticed that containerView.frame returns the size of the component from the storyboard, see pic 2. The frame obtained by calling on the containerView.frame is the one before the view is resized to meet all constrains. How do i get the proper values?
Placing the same code inside viewDidLayoutSubviews() instead of viewDidLoad() solved the issue.
I am adding a UIView to a container view programmatically, (the container view however is created in storyboard). Here is the code:
class ViewController: UIViewController{
#IBOutlet weak var dwView: UIView!
private var dwSelector = dwSelectorView()
override func viewDidLoad(){
super.viewDidLoad()
addDWSelector()
}
func addDWSelector(){
dwSelector.setTranslatesAutoresizingMaskIntoConstraints(false)
dwSelector.frame = CGRectMake(self.dwView.bounds.origin.x, self.dwView.bounds.origin.y, self.dwView.bounds.width / 2.0, self.dwView.frame.height)
println("dw height: \(self.dwView.frame.height)")
//prints 568, way too large of a value
self.dwView.addSubview(dwSelector)
}
}
The heigh of dwView is 123 in storyboard but the print state printed 568 and so now this is what it looks like:
You should always not rely on -(void)viewDidLoad since view bounds is incorrect at this point or - (void)viewWillAppear if you are using auto layout to set your view's frame. If you layout view in UIViewController, viewDidLayoutSubviews() is a appropriate place, if you layout subviews in UIView, it is layoutSubviews().
Check this article to get more details:Where to progmatically lay out views in iOS 5 (and handling orientation changes)
have you tried to call addDWSelector() in viewWillAppear()?
I'm building a weather app as a beginner project. Say I wanted a custom view that consisted on many UILabels for temp, humidity, precipitation, etc. The idea is that this custom UIView would be used several times for every city the user has saved. If the user has 3 cities saved, the custom view would have 3 instances.
What is the best way to do this? I'm trying to subclass a UIView. Originally I was overriding drawRect(rect: CGRect) and defining my UILabels there. That just didn't feel right. And it wouldn't get alloc/inited until way later, after I was trying to update the label text in the completion handler on NSURLSession.
Or should I be overriding init() which makes me do this:
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
And I have no idea what that means. Then I'm forced to doing something like this when I try to init with frame on the root VC.
override init(frame: CGRect) { super.init(frame: frame) }
Can someone walk me through the best approach? I have something like below but I get a nil value right when I'm trying to add the UILabels to subview of the custom class.
class ViewTemplate: UIView {
var tempLabel: UILabel!
var humidityLabel: UILabel!
override init () {
tempLabel = UILabel()
tempLabel.frame = CGRectMake(halfScreenWidth - 130, 120, 260, 130)
tempLabel.textColor = UIColor.whiteColor()
tempLabel.backgroundColor = UIColor.clearColor()
// similar stuff for humidityLabel
super.init()
addSubview(tempLabel)
}
override init(frame: CGRect) { super.init(frame: frame) }
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If not quite sure where the nil is coming from. But most importantly, I'm looking for the best practice in doing this.
Thanks!
For creating the individual View:
Create new View file. Subclass of UIView. Be sure to also create the xib file when doing this.
Draw your items in storyboard (labels, etc.) and connect them to the swift file
Be certain that you connect them in storyboard by making the xib file a Custom Class of the swift file you just created.
Instantiate them inside the awakeFromNib() method. Be sure to set their default values for text if they are labels. Otherwise they will come up as nil when instantiated and your app will crash.
import UIKit
class MyView: UIView {
#IBOutlet weak var templabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
self.tempLabel.text = "95 degrees"
}
For loading into viewcontroller:
1a. Create a viewcontroller that implements UIScrollView.
1b. Place a UIScrollview in your storyboard and connect it to that viewcontroller
class MyWeatherViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet var scrollView: UIScrollView!
2a. In ViewDidLoad, create instances of the UIView you are looking to create. Populate them individually.
2b. In ViewDidLoad, set the scrollview width to the width of a single View. Set the height to the height of the single View * the number of Views you wish to display
var view1: MyView = MyView()
var view2: MyView = MyView()
let viewArray[MyView!] = [view1, view2]
for items in viewArray {
var frame = scrollView.bounds
//Set the origin of the views
frame.origin.x = 0.0
frame.origin.y = frame.size.height * items
//create a view out of the object provided, define it's frame, and add to scrollview
let viewToAdd = viewArray[items].view
newPageView.frame = frame
scrollView.addSubview(viewToAdd)
//Set up your labels to be displayed. You must do this AFTER you load the load into the scrollview
viewArray[items].tempLabel.text = "105 degrees"
}
Keep in mind that you will want to set up another data model to hold the information that is to be displayed. I would recommend creating an array that holds the information you wish to display, so you can do viewArray[items].tempLabel.text = tempArray[items] with all the labels you wish to set.
This should align them vertically with the ability to scroll and see all items.
This will also let dynamically decide how many views to add based on cities the user has saved. Just modify the logic to read however many cities the user has etc.
You're on the right track.
Declare the properties for the labels. It looks like you've already done that. Personally, I would declare those as constants with let instead of var because you should always have those labels.
Override the init(frame: CGRect) method. Again, it looks like you've already started this. The reason you should override init(frame: CGRect) instead if init() is because init() just calls init(frame: CGRectZero). If you only implement init() then some of your properties may not get initialized (although this is way less of an issue with Swift since you can declare properties as constants so the compiler throws an error). In your initializer you're going to want to init all your labels & set them up right before you call super.init(frame). After the super.init(frame) method is called you should add the labels as subviews of self.
Override layoutSubviews() if you are laying your UI out programmatically. This method is where you will do all the sizing and laying out of your subviews. I do everything this way so it's what I'm most familiar with. If you're using AutoLayout I believe it's best practice to apply those constraints in the class's initializer since they should only ever need to be set up once.
Optionally, you may also want to override sizeThatFits(). This will ensure your view is always the proper size when sizeToFit() is called on it.
This goes not just for UIView but any subclass of it.
You can use a UIViewController extension and initialize a UIView function that takes a UIView. You can use this anywhere in your project.
extension UIViewController {
func setView(_ view: UIView){
view.backgroundColor = UIColor()
view.layer.cornerRadius = 5.0
// customize your view
}
}
Then: Declare you view in your Controller and pass it when the function is called on viewDidLoad()
let myView = UIView()
setView(myView)