Sorry for the extremely basic question, but I'm trying to better understand MVC and how it pertains to more advanced screens.
Say I have a exampleView, that has multiple labels in it and I want to add this view to a ScrollView using addSubview. Should the ScrollView be instantiated in the View Controller and then calling self.view.addSubview(scrollView) and self.scrollView.addSubview(exampleView), or would you turn exampleView into a scrollView and just adding self.view.addSubview(exampleScrollView).
My friend has told me that there should never be programmatic constraints in the view controller if we're following MVC, but I'm not sure if that's true or not.
Constraints have nothing to do with MVC. MVC is a programming architecture or design pattern that attempts to divide the program into three categories: data (model), the visual objects that often represent the data (view), and the objects that bind them together (controller). And constraints are simply a way of positioning visual objects. The way you position visual objects has nothing to do with the architecture you decide for your program; they are completely independent of each other.
And the idea that a view controller should never have programmatic constraints is just ridiculous. Not only do constraints (let alone programmatic constraints) have nothing to do with MVC, forbidding the use of programmatic constraints for any reason is a sign of a very poor developer. You can code an entire application programmatically but you cannot even come close to coding an entire application non-programmatically.
The interface builder, in my opinion, is mostly there for professional programmers who program for a number of clients and find themselves constantly adding view objects (views, labels, buttons, scroll views, table views, etc.) because it's drag and drop. I personally find the whole IB experience very unappealing.
To your question about a scroll view, use this very basic template as a guide. Add the scroll view to the view and add the subviews to the scroll view. To make it work, however, you must anchor the objects in the scroll view to the edges of the scroll view (especially the bottom) so that its content view stretches and it scrolls.
class Slickdaddy: UIViewController {
private let scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
addScrollView()
addAView()
}
private func addScrollView() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
private func addAView() {
let label = UILabel()
label.text = "slick daddy"
label.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(label)
label.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16).isActive = true
label.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 32).isActive = true
label.sizeToFit()
label.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -32).isActive = true
}
}
Related
I created stackView and added three labels to it. Also I put these constraints
let nameLabel = UILabel()
//set .text, .mode and other for nameLabel
let ellipsisLabel = UILabel()
//set .text, .mode and other for ellipsisLabel
let amountAndMeasureLabel = UILabel()
//set .text, .mode and other for amountAndMeasureLabel
for label in [nameLabel, ellipsisLabel, amountAndMeasureLabel] {
stackView.addArrangedSubview(label)
}
NSLayoutConstraint.activate([
nameLabel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 0.0),
nameLabel.trailingAnchor.constraint(equalTo: ellipsisLabel.leadingAnchor, constant: 0.0),
nameLabel.widthAnchor.constraint(equalToConstant: nameLabel.intrinsicContentSize.width),
amountAndMeasureLabel.leadingAnchor.constraint(equalTo: ellipsisLabel.trailingAnchor, constant: 0.0),
amountAndMeasureLabel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: 0.0),
amountAndMeasureLabel.widthAnchor.constraint(equalToConstant: amountAndMeasureLabel.intrinsicContentSize.width)
])
nameLabel.frame.width // 0
ellipsisLabel.frame.width // 0
amountAndMeasureLabel.frame.width // 0
I will demonstrate the situation on .xib for better understanding (I don't use this .xib)
But when I tried to get their .frame.width I got zeros.
Just activating constraints doesn't cause a view to layout. If you need stackView to immediately layout its subviews, you need to tell it to:
stackView.layoutIfNeeded()
Otherwise the system will automatically collect and apply all layout updates together the next time it's needed. Since forcing a layout may lead to unnecessary computations (if something later in the draw cycle changes the layout), you should generally avoid calling .layoutIfNeeded() unless you really need it. But it's fully supported if you do.
There is also .setNeedsLayout(), which tells the system that a view needs layout during the next draw cycle. You should use this if something changes layout in a way that the system won't notice. You generally don't need this if your changes are through UIKit itself. UIStackView knows that it needs layout after you call .addArrangedSubview. But if you change some piece of data that your layout relies on, you may need to tell UIKit.
Whenever you scroll, the title view bugs out and rapidly switches between two positions. The label manages to stay within the constraints of the view, and it is just the view's position that bugs.
I've made a gif of the issue if it is still unclear or you need to see an example of the problem.
https://gph.is/g/EJ09l9Z
I've tried playing around with constraints to no avail. I still believe that the issue is to do with constraints, although I am not an expert by any means in Xcode or swift so I could be wildly wrong.
These are the constraints that I have been working with and around in attempts to get this working.
titleView.leftAnchor.constraint(equalTo: scrollView.contentView.leftAnchor).isActive = true
titleView.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor, constant: 6).isActive = true
titleView.rightAnchor.constraint(equalTo: scrollView.contentView.rightAnchor, constant: 6).isActive = true
titleView.bottomAnchor.constraint(equalTo: timeView.topAnchor).isActive = true
titleLabel.leftAnchor.constraint(equalTo: titleView.leftAnchor, constant: 10).isActive = true
titleLabel.topAnchor.constraint(equalTo: titleView.topAnchor).isActive = true
titleLabel.rightAnchor.constraint(equalTo: titleView.rightAnchor).isActive = true
titleLabel.bottomAnchor.constraint(equalTo: timeView.topAnchor).isActive = true
titleLabel.layer.masksToBounds = true
The goal is for the title to stay in the one position as the view is scrolled, like the labels and views beneath it. Feel free to suggest any other fixes or ask for more code, I just don't know what the problem would be if not constraints.
Also for clarification, I am not using a UIScrollView, but rather an OLEContainerScrollView.
https://github.com/ole/OLEContainerScrollView
https://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/
I want to display two textfields in single tableview cell along with some separator between of them both. Those, two textfields has equal space to edges and with the separator. I want to create this in programmatically in tableview cell class in Swift language.
I want this to be fit in all dimension devices.
Note : I am not using autolayout.
Any suggestions?
I highly recommend you to use UIStackViews as it sounds like a perfect use case for your question.
Definition of a UIStackView in the Documentation:
A streamlined interface for laying out a collection of views in either
a column or a row.
It would take care of the resizing behaviour of your views and you could also easily control the margins and the spacing between your elements.
You can use UIStackViews with storyboards or programmatically depending on what you are looking for.
You may also want to read this guide in the documentation to learn more about UIStackViews.
How to use:
Say you have two text fields, textFieldA, textFieldB and your separator view named separatorView.
Here is how you could do setup your UIStackViews programmatically inside your UITableViewCell subclass:
// Create and configure your stack view
let stackView = UIStackView()
stackView.axis = UILayoutConstraintAxis.horizontal
stackView.distribution = UIStackViewDistribution.fillProportionally
stackView.alignment = UIStackViewAlignment.fill
stackView.spacing = 20.0
// Add your textfields and your separator view to the stack view
stackView.addArrangedSubview(textFieldA)
stackView.addArrangedSubview(separatorView)
stackView.addArrangedSubview(textFieldB)
stackView.translatesAutoresizingMaskIntoConstraints = false
// Add your stack view:
self.addSubview(stackView)
// Configure the constraints for your stack view
// (Of course you can set up your stack view the way you want and you don't have to absolutely use constraints, but it's just for the example)
stackView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -20).isActive = true
stackView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 20).isActive = true
stackView.topAnchor.constraint(equalTo: self.topAnchor, constant: 20).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 200.0).isActive = true
It will save you a lot of time as you don't need to add constraints everywhere for all your views, but only for the stack view. The stack view will then take care of its arranged subviews.
One thing you may need in your case, is changing the hugging and compression resistance values for your views, to make sure it looks great on any screens.
As you can see you there are a lot of properties that you can set on your stack view, so just feel free to try different values and set it up the way you like!
Use stack view with axis horizontal and distribution is equal to fillProportionally
I'm making a survey iMessage app (yeah I know) and having a problem moving between the presentation modes. The series of screenshots below shows that when the app launches, all is fine in the compact mode. When expanded everything is still correct but then when I go back to compact the content is shifted down by what looks like the same height as the large messages nav bar (86 I believe)
I've tried setting the top constraint to be -86 when switching back to the compact view, however, this either does nothing or sends it back to where is should be and then subtracts 86 so it dissapears too high up. I've based this project on the IceCream example project from app so not sure where this issue is coming from (probably autolayout but everything is pinned to the layout guides)
Here's the code that adds the view controller:
func loadTheViewController(controller: UIViewController) {
// Remove any existing child controllers.
for child in childViewControllers {
child.willMove(toParentViewController: nil)
child.view.removeFromSuperview()
child.removeFromParentViewController()
}
// Embed the new controller.
addChildViewController(controller)
controller.view.frame = view.bounds
controller.view.translatesAutoresizingMaskIntoConstraints = true
view.addSubview(controller.view)
controller.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
controller.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
controller.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
controller.didMove(toParentViewController: self)
}
I've been working on this for what feels forever so any suggestions welcome.
You are setting up constraints on view but you have set translatesAutoresizingMaskIntoConstraints to true. The autoresizing mask constraints will likely conflict with the constraints you are adding, cause unexpected results. You should change to:
controller.view.translatesAutoresizingMaskIntoConstraints = false
Also rather than pinning to view.topAnchor, you should pin to the topLayoutGuide, which will take the top navigation bar into account.
controller.view.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
Similarly,
controller.view.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
I'm developing iMessage Extension app.
My problem is that the collection view is visible behind navigation bar(I think navbar is automatically produced by iMessage Extension) in expand mode.
We can see collectionview (with transparency) behind navbar.
Is there anybody who has experience in this area?
Looking for any help.
After give Constraints(Top:0,Left:0,Right:0,Bottom:0),you should give Top Constraints of CollectionView to Top Layout Guide.Bottom ratherthan Superview.Top like this:
CollectionView.Top = Top Layout Guide.Bottom is set to 0.
In viewDidLoad():
CollectionView.frame = view.bounds
CollectionView.translatesAutoresizingMaskIntoConstraints = false
CollectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
CollectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
CollectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
CollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
Referring to #seggy question comment:
have you set constraint perfectly?
You answered:
Yeah, of course, top:0, left:0, right:0, bottom: 0
The top constraint should be equals to 64 (it seems the top constraint is between the collectionView and the container view, not between the collectionView and the navigation bar).
Also, you might need to change the bottom constraint's constant value.
Hope this helped.