I have properly set up an Viewcontroller containing an UIView with class "MGLMapView", what I am trying add an button to the Viewcontroller, but when I run the app the button does not show.
Same if I do an external Viewcontroller and overlay the MapViewController, it runs, but it does not show.
Why is that? have anybody had a similar problem? and how do I solve it?
Thanks in advance!
I don't know about the MGLMapView but it should adhere to same rules as any other UIView object.
This example is based on a MKMapView but it should be analog for MGMapView. Put this code in your VC and add setUpViews() to your viewDidLoad() method.
Note: this code should put the MKMapView at the bottom of the view stack and the map view should take up the entire view (except the top 64 pts--I assumed a UINavigationView might be in use--if not just change 64.0 to 0.0 in the constraints). Then it adds a UIButton on top of the MKMapView centered in the x direction with a width of 150 a height of 40 and 20 from the bottom of the view.
let map: MKMapView = {
let view = MKMapView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let button: UIButton = {
let view = UIButton()
// Add or change attributes as you need
view.backgroundColor = UIColor.green
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
fileprivate func setUpViews(){
// Add the map first
self.view.addSubview(map)
// Add the button now it will be on top of the map view
self.view.addSubview(button)
map.delegate = self;
NSLayoutConstraint.activate([
map.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 64.0),
map.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
map.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
map.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
])
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0.0),
button.heightAnchor.constraint(equalToConstant: 40.0),
button.widthAnchor.constraint(equalToConstant: 150.0),
button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -20.0),
])
}
Related
I have created a UINavigationController class which allows users to Log out and displays the title of the app. I then added a UITabController as its only viewController in its viewControllers array:
let homeController = HomeController()
viewControllers = [homeController]
This UITabController (HomeController()) is then populated with a few UIViewControllers - one of which will display a Profile page. This is my first project in which I won't be using the storyboard so things have been a great challenge!
I have created a UIImageView object within the class and within my viewDidLoad for my profile page, I have used:self.view.addSubview(imageView)to add to view and then:imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true in an attempt to anchor the image to the bottom of the UINavigationController bar at the top of the screen.However the result places the image at the very top of the screen, as if the Navigation Bar isn't recognised as being visible. I read in this article: https://medium.com/#amlcurran/a-quick-guide-to-laying-out-views-in-ios-471e92deb74, that '.topLayoutGuide.bottomAnchor' represents the bottom of the navigation bar, but this has now been depreciated to my example above.
Does anyone have any ideas as to what is wrong?And also any good resources for me to fully understand programmatically constraining my elements!Thanks all!!
https://gist.github.com/JoeMcGeever/a5ce3be94fc49a8f27b1a2867bd9495b
That link shows some of the code so far - I am aware the other elements are also pinned to the top; I am just trying to fix this error regarding the navigation bar first.
Image showing what the view displays at the moment
You should really go through several auto-layout tutorials. There are many, many of them out there. After you've worked through a dozen or so, you should have a good idea of what needs to be done.
In the meantime, here is your ProfileViewController edited to give you an idea of what you were doing wrong:
class ProfileViewController : UIViewController {
let imageView : UIImageView = { //creates an image view with the name "imageView"
let image = UIImage(named: "logo")
let imageView = UIImageView(image: image)
return imageView
}()
let usernameLabel = UILabel()
// if you're addint a target referring to "self", this must be a lazy var
lazy var editProfileButton : UIButton = {
let editButton = UIButton()
editButton.backgroundColor = .orange
editButton.setTitle("Edit Profile", for: .normal)
editButton.setTitleColor(.white, for: .normal)
editButton.addTarget(self, action: #selector(handleEdit), for: .touchUpInside)
return editButton
}()
#objc func handleEdit(){
//edit handle button
print("Edit profile")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
self.title = "Profile"
usernameLabel.text = "Username Here"
// we're going to use auto-layout
[imageView, usernameLabel, editProfileButton].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
self.view.addSubview(imageView)
self.view.addSubview(usernameLabel)
self.view.addSubview(editProfileButton)
// need FULL sets of constraints, not just TOP anchors
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// image view at upper-left
// image view 8-pts from top of safe area
imageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
// and 8-pts from left
imageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
// give it a width of, for example, one-quarter the view width
imageView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.25),
// give it a 1:1 ratio (square)
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
// button at upper-right
editProfileButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
editProfileButton.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
// no width or height... let the button size itself
// label below the image view
usernameLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 8.0),
usernameLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
// no width or height... let the label size itself
])
// give the name label a background color so we can see its frame
usernameLabel.backgroundColor = .cyan
}
}
Review the comments in the code to understand what I did.
Result will look about like this (I used a random image for the logo):
If you want to anchor your imageView to the top of safeArea, you have to constraint it to the topAnchor like this:
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
I’m trying to rip a view from a stackView that is embedded in a scrollView and then reposition said view in the same location but in another view at the same level in the view hierarchy as the scrollView.
The effect I’m trying to achieve is that I’m animating the removal of a view— where the view would be super imposed in another view, while the scrollView would scroll up and new view would be added to the stackView all while the view that was ripped fades out.
Unfortunately, achieving this effect remains elusive as the rippedView is position at (x: 0, y: 0). When I try force a new frame onto this view its tough because Im guessing the pixel perfect correct frame. Here’s a bit of the code from my viewController:
/*
I tried to make insertionView and imposeView have the same dimensions as the scrollView and
the stackView respectively as I thought if the rippedView’s original superView is the same
dimensions as it’s new superView, the rippedView would be positioned in the same place
without me needing to alter its frame.
*/
let insertionView = UIView(frame: scrollView.frame)
let imposeView = UIView(frame: stackView.frame)
rippedView.removeFromSuperview()
insertionView.addSubview(imposeView)
imposeView.addSubview(rippedView)
let newFrame = CGRect(x: 0, y: 450, width: rippedView.intrinsicContentSize.width, height:
rippedView.intrinsicContentSize.height)
rippedView.frame = newFrame
self.view.addSubview(insertionView)
Before removing rippedView, get it's actual frame:
let newFrame = self.view.convert(rippedView.bounds, from: rippedView)
The issue you are hitting is likely due to the stackView's arranged subviews having .translatesAutoresizingMaskIntoConstraints set to false. I believe this happens automatically when you add a view to a stackView, unless you specify otherwise.
A stackView's arranged subviews have coordinates relative to the stackView itself. So the first view will be at 0,0. Since you are adding a "container" view with the same frame as the stackView, you can use the same coordinate space... but you'll need to enable .translatesAutoresizingMaskIntoConstraints.
Try it like this:
#objc func btnTapped(_ sender: Any?) -> Void {
// get a reference to the 3rd arranged subview in the stack view
let rippedView = stackView.arrangedSubviews[2]
// local var holding the rippedView frame (as set by the stackView)
// get it before moving view from stackView
let r = rippedView.frame
// instantiate views
let insertionView = UIView(frame: scrollView.frame)
let imposeView = UIView(frame: stackView.frame)
// add imposeView to insertionView
insertionView.addSubview(imposeView)
// add insertionView to self.view
self.view.addSubview(insertionView)
// move rippedView from stackView to imposeView
imposeView.addSubview(rippedView)
// just to make it easy to see...
rippedView.backgroundColor = .green
// set to TRUE
rippedView.translatesAutoresizingMaskIntoConstraints = true
// set the frame
rippedView.frame = r
}
Here's a full class example that you can run directly (just assign it to a view controller):
class RipViewViewController: UIViewController {
let aButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .red
v.setTitle("Testing", for: .normal)
return v
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .systemBlue
return v
}()
let stackView: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.spacing = 8
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(aButton)
view.addSubview(scrollView)
scrollView.addSubview(stackView)
let g = view.safeAreaLayoutGuide
let sg = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
aButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 16.0),
aButton.centerXAnchor.constraint(equalTo: g.centerXAnchor, constant: 0.0),
scrollView.topAnchor.constraint(equalTo: aButton.bottomAnchor, constant: 40.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
stackView.topAnchor.constraint(equalTo: sg.topAnchor, constant: 40.0),
stackView.leadingAnchor.constraint(equalTo: sg.leadingAnchor, constant: 20.0),
stackView.trailingAnchor.constraint(equalTo: sg.trailingAnchor, constant: 20.0),
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -40.0),
stackView.bottomAnchor.constraint(equalTo: sg.bottomAnchor, constant: 20.0),
])
for i in 1...5 {
let l = UILabel()
l.backgroundColor = .cyan
l.textAlignment = .center
l.text = "Label \(i)"
stackView.addArrangedSubview(l)
}
aButton.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
}
#objc func btnTapped(_ sender: Any?) -> Void {
// get a reference to the 3rd arranged subview in the stack view
let rippedView = stackView.arrangedSubviews[2]
// local var holding the rippedView frame (as set by the stackView)
// get it before moving view from stackView
let r = rippedView.frame
// instantiate views
let insertionView = UIView(frame: scrollView.frame)
let imposeView = UIView(frame: stackView.frame)
// add imposeView to insertionView
insertionView.addSubview(imposeView)
// add insertionView to self.view
self.view.addSubview(insertionView)
// move rippedView from stackView to imposeView
imposeView.addSubview(rippedView)
// just to make it easy to see...
rippedView.backgroundColor = .green
// set to TRUE
rippedView.translatesAutoresizingMaskIntoConstraints = true
// set the frame
rippedView.frame = r
}
}
On the latest Xcode (10.1), I'm having trouble adding a fixed size UIView to my UIStackView using programmatic constraints. I think this should be straightforward, but I don't understand where extra constraints are coming from (that UIKit has to break some to layout the views).
The problem is that I am expecting a blue UIView of 100x100. The reality is that the blue UIView is 100% screen width & 100% screen height.
I realize UIStackView uses intrinsicContentSize, but how do I set that correctly using programmatic constraints?
The following is a working playground with an UIStackView & a vanilla UIView added.
Note: if I add the blue UIView directly to the ViewController's view, the size is correct at 100x100 at origin (0,0). Adding it to the stack view causes constraint conflicts.
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
// vertical stack view (full screen)
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.topAnchor),
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.widthAnchor.constraint(equalTo: view.widthAnchor),
])
// view (100x100)
let fixedSizeView = UIView()
fixedSizeView.translatesAutoresizingMaskIntoConstraints = false
fixedSizeView.backgroundColor = .blue
stackView.addArrangedSubview(fixedSizeView)
NSLayoutConstraint.activate([
fixedSizeView.widthAnchor.constraint(equalToConstant: 100),
fixedSizeView.heightAnchor.constraint(equalToConstant: 100),
])
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
The main purpose of a UIStackView is to arrange its subviews.
So, you are constraining your stack view to "fill the screen" and then adding your "blue view" as an arranged subview ... at which point the stack view will "take over" the arrangement of the blue view.
Assuming you are using a stack view because you are planning on adding additional views, you can either allow the subviews to determine the stack view's frame (that is, don't constrain your stack view's width and/or height), or you need to change the stack view's .alignment and/or .distribution properties.
Here is a modification of your playground page to put the 100 x 100 blue view centered in the superview:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
// vertical stack view (full screen)
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
view.addSubview(stackView)
// NSLayoutConstraint.activate([
// stackView.topAnchor.constraint(equalTo: view.topAnchor),
// stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// stackView.widthAnchor.constraint(equalTo: view.widthAnchor),
// ])
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
// view (100x100)
let fixedSizeView = UIView()
fixedSizeView.translatesAutoresizingMaskIntoConstraints = false
fixedSizeView.backgroundColor = .blue
stackView.addArrangedSubview(fixedSizeView)
NSLayoutConstraint.activate([
fixedSizeView.widthAnchor.constraint(equalToConstant: 100),
fixedSizeView.heightAnchor.constraint(equalToConstant: 100),
])
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
and, here's a modification where two views - blue and red, each at 100 x 100 - get added to a stack view that is constrained to the top of the superview, with .alignment set to .center:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
// vertical stack view (full screen)
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.alignment = .center
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.topAnchor.constraint(equalTo: view.topAnchor),
])
// view (100x100)
let fixedSizeBlueView = UIView()
fixedSizeBlueView.translatesAutoresizingMaskIntoConstraints = false
fixedSizeBlueView.backgroundColor = .blue
stackView.addArrangedSubview(fixedSizeBlueView)
NSLayoutConstraint.activate([
fixedSizeBlueView.widthAnchor.constraint(equalToConstant: 100),
fixedSizeBlueView.heightAnchor.constraint(equalToConstant: 100),
])
// view (100x100)
let fixedSizeRedView = UIView()
fixedSizeRedView.translatesAutoresizingMaskIntoConstraints = false
fixedSizeRedView.backgroundColor = .red
stackView.addArrangedSubview(fixedSizeRedView)
NSLayoutConstraint.activate([
fixedSizeRedView.widthAnchor.constraint(equalToConstant: 100),
fixedSizeRedView.heightAnchor.constraint(equalToConstant: 100),
])
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
I have a UIScrollView. It has a stack view. And this stack view contains 12 buttons. (Horizontal scroll view)
Stackview constraints :- top,leading,trailing,bottom to the scroll view and equal widths to the scroll view.
My problem is every time when I run, stack view width limits to the scroll view width and buttons are too small acording to the width of the stack view and my scroll view is not scrollable.
How to make this scrollable
Step-by-Step for setting this up in IB / Storyboards...
Add a view - height 50 leading/top/trailing - blue background
add a scrollview to that view - pin leading/top/trailing/bottom to 0 - set scrollview background to yellow so we can see where it is
add a button to the scroll view
duplicate it so you have 12 buttons
group them into a stack view, and set the stack view's constraints to 0 leading/top/trailing/bottom
and set the stack view's distribution to "equal spacing"
result running in simulator (with no code at all):
and the buttons scroll left and right... no code setting of .contentSize...
So you want this:
Here's how I did it in Xcode 8.3.3.
New Project > iOS > Single View Application.
Open Main.storyboard.
Drag a scroll view into the scene.
Pin top, leading, and trailing of the scroll view to 0. Set height to 30.
Drag a horizontal stack view into the scroll view.
Pin all four edges of the stack view to 0.
Set stack view spacing to 4.
Drag twelve buttons into the stack view.
Set target device to iPhone SE.
Build & run.
Resulting document outline:
If you make your Stackview width equal to the scrollview width, then that's all you'll get, and of course it won't scroll.
Don't give your Stackview a width constraint... let the buttons "fill it out".
Edit: Here is a simple example that you can run directly in a Playground page:
import UIKit
import PlaygroundSupport
class TestViewController : UIViewController {
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
return v
}()
let stackView : UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .horizontal
v.distribution = .equalSpacing
v.spacing = 10.0
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// add the scroll view to self.view
self.view.addSubview(scrollView)
// constrain the scroll view to 8-pts on each side
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8.0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
// add the stack view to the scroll view
scrollView.addSubview(stackView)
// constrain the stackview view to 8-pts on each side
// this *also* controls the .contentSize of the scrollview
stackView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 8.0).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 8.0).isActive = true
stackView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: -8.0).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -8.0).isActive = true
// add ten buttons to the stack view
for i in 1...10 {
let b = UIButton()
b.translatesAutoresizingMaskIntoConstraints = false
b.setTitle("Button \(i)", for: .normal)
b.backgroundColor = .blue
stackView.addArrangedSubview(b)
}
}
}
let vc = TestViewController()
vc.view.backgroundColor = .yellow
PlaygroundPage.current.liveView = vc
I am having an UIButton at my UIScrollView and when i zoom my scroll view the button position is not fix...is there any way to make it fix at one location?
Place/set your button over scroll view (not inside scroll view) as shown here in this snapshot. And also set button constraints (position) with respect to super view of your scrollview.
Here is ref. snapshot of hierarchy of position of each view over each-other.
Since iOS 11, UIScrollView has an instance property called frameLayoutGuide. frameLayoutGuide has the following declaration:
var frameLayoutGuide: UILayoutGuide { get }
The layout guide based on the untransformed frame rectangle of the scroll view.
Use this layout guide when you want to create Auto Layout constraints that explicitly involve the frame rectangle of the scroll view itself, as opposed to its content rectangle.
The following Swift 5.1 / iOS 13 UIViewController implementation shows how to use frameLayoutGuide in order to center a UIButton inside a UIScrollView.
import UIKit
class ViewController: UIViewController {
let scrollView = UIScrollView()
let button = UIButton(type: .system)
let imageView = UIImageView(image: UIImage(named: "LargeImage"))
override func viewDidLoad() {
super.viewDidLoad()
// Set scrollView constraints
scrollView.contentInsetAdjustmentBehavior = .never
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
// Set imageView constraints
scrollView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
imageView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
imageView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor)
])
// Set button constraints (centered)
button.setTitle("Button", for: .normal)
scrollView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: scrollView.frameLayoutGuide.centerXAnchor),
button.centerYAnchor.constraint(equalTo: scrollView.frameLayoutGuide.centerYAnchor)
])
}
}
If you want your button to stay at a fixed size and position when you modify your scrollview, the best way would be to not have it be in the scrollview at all. Add the button to the parent view of the scrollview, then it won't be affected by any changes to the scrollview and can be overlaid on top of it.
You should position your button outside the scroll view and anchor it to the bottom of the page. This way the content in your scroll view will change but your button will remain statically fixed on the view.