Reputation: 9
I'm trying to make ui using snapkit and practice MVVM pattern, but when I try to add stackview I keep getting an error that says
'Unable to activate constraint with anchors <NSLayoutYAxisAnchor:0x600001753fc0 "UIStackView:0x10380aba0.centerY"> and <NSLayoutYAxisAnchor:0x600001753a00 "UIImageView:0x103905b00.centerY"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
When I comment out the stackview code line, everything works well, so I guess there is something wrong with the stackview.
This is Post.swift code.
struct Post {
let userHandle: String
let userProfileImage: String
let location: String
let selfieImage: String
let rearViewImage: String
let caption: String
init(userHandle: String, userProfileImage: String, location: String, selfieImage: String, rearViewImage: String, caption: String) {
self.userHandle = userHandle
self.userProfileImage = userProfileImage
self.location = location
self.selfieImage = selfieImage
self.rearViewImage = rearViewImage
self.caption = caption
}
}
This is PostView.swift code.
import UIKit
import SnapKit
class PostView: UIView {
var userProfileImageName: String = "" {
willSet {
userProfileImageView.image = UIImage(named: newValue)
print("userProfileImage: \(newValue)")
}
}
var selfieImageName: String = "" {
willSet {
selfieImageView.image = UIImage(named: newValue)
print("selfieImage: \(newValue)")
}
}
var rearImageName: String = "" {
willSet {
rearViewImageView.image = UIImage(named: newValue)
print("rearImage: \(newValue)")
}
}
let userProfileImageView: UIImageView = {
let imageView = UIImageView()
imageView.clipsToBounds = true
return imageView
}()
lazy var userHandleLabel: UILabel = {
let label = UILabel()
label.textAlignment = .natural
label.textColor = .white
label.font = .systemFont(ofSize: 13, weight: .semibold)
label.backgroundColor = .lightGray
return label
}()
lazy var locationLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10)
label.textColor = .lightGray
label.backgroundColor = .black
return label
}()
var stackView: UIStackView {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 5
return stackView
}
let selfieImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 10.0
return imageView
}()
let rearViewImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 10.0
return imageView
}()
let captionLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 15.0, weight: .semibold)
label.textColor = .white
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .darkGray
addSubview(userProfileImageView)
addSubview(stackView)
stackView.addArrangedSubview(userHandleLabel)
stackView.addArrangedSubview(locationLabel)
addSubview(rearViewImageView)
addSubview(captionLabel)
// constraint
userProfileImageView.snp.makeConstraints { make in
make.top.equalToSuperview().inset(5)
make.leading.equalToSuperview().inset(5)
make.width.equalTo(50)
make.height.equalTo(userProfileImageView.snp.width)
}
stackView.snp.makeConstraints { make in
make.centerY.equalTo(userProfileImageView.snp.centerY)
make.leading.equalTo(userProfileImageView.snp.trailing).offset(5)
}
rearViewImageView.snp.makeConstraints { make inmake.top.equalTo(userProfileImageView.snp.bottom).offset(10)
make.leading.trailing.equalToSuperview()
// height == width * 4/3
make.height.equalTo(rearViewImageView.snp.width).multipliedBy(4.0 / 3.0)
// don't need this
//make.centerX.equalToSuperview()
}
captionLabel.snp.makeConstraints { make in
make.top.equalTo(rearViewImageView.snp.bottom).offset(5)
make.leading.equalTo(rearViewImageView.snp.leading)
make.trailing.equalTo(rearViewImageView.snp.trailing)
make.bottom.equalToSuperview().inset(10)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
This is PostViewModel.swift code.
class PostViewModel {
private let post: Post
init(post: Post) {
self.post = post
}
var userHandle: String {
return post.userHandle
}
var userProfileImage: String {
return post.userProfileImage
}
var location: String {
return post.location
}
var selfieImage: String{
return post.selfieImage
}
var rearViewImage: String {
return post.rearViewImage
}
var caption: String {
return post.caption
}
}
extension PostViewModel {
func configure(_ view: PostView){
view.locationLabel.text = location
view.captionLabel.text = caption
view.rearImageName = rearViewImage
view.selfieImageName = selfieImage
view.userProfileImageName = userProfileImage
view.userProfileImageView.layer.cornerRadius = 25
print("== \(view.userProfileImageView.layer.cornerRadius)")
}
}
This is the viewcontroller.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let newPost = Post(userHandle: "userHandle", userProfileImage: "profileImage.png", location: "A location", selfieImage: "selfieImage.jpg", rearViewImage: "rearImage.jpg", caption: "BeReal")
//let postView = PostView()
let postViewModel = PostViewModel(post: newPost)
let postView = PostView()
postViewModel.configure(postView)
view.addSubview(postView)
postView.snp.makeConstraints { make in
//make.centerY.equalToSuperview()
make.leading.trailing.equalToSuperview()
}
postView.backgroundColor = .lightGray
}
}
I also tried making the stackview's constraint with equalToSuperview(), but I get Fatal error: Expected superview but found nil when attempting make constraint
equalToSuperview.
error, so I guess stackview is not being added...?
I also double-checked that I'm adding subview before making constraints. I just can't figure out what causes this error.
Upvotes: 0
Views: 108
Reputation: 77672
You must have left out some code ... because I don't get the no common ancestor
error you're getting.
However, we can make a couple changes to "clean up" your constraints... see the inline comments in this modification to your code.
A tip: when initially working on your layout, give the UI elements contrasting background colors to make it easy to see the framing.
UIView subclass:
class UserView: UIView {
let userProfileImageView = UIImageView()
let stackView = UIStackView()
let userHandleLabel = UILabel()
let locationLabel = UILabel()
let rearViewImageView = UIImageView()
let captionLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .darkGray
// let's use some contrasting background colors so we can see the framing
userProfileImageView.backgroundColor = .red
rearViewImageView.backgroundColor = .blue
userHandleLabel.backgroundColor = .yellow
locationLabel.backgroundColor = .cyan
captionLabel.backgroundColor = .green
// let's set images for the image views
if let img = UIImage(systemName: "person") {
userProfileImageView.image = img
userProfileImageView.tintColor = .lightGray
}
if let img = UIImage(systemName: "r.square") {
rearViewImageView.image = img
rearViewImageView.tintColor = .lightGray
}
// some text in the labels so we can see them
userHandleLabel.text = "userHandleLabel"
locationLabel.text = "locationLabel"
captionLabel.text = "captionLabel"
// now let's add the UI elements to the view hierarchy
addSubview(userProfileImageView)
addSubview(stackView)
stackView.addArrangedSubview(userHandleLabel)
stackView.addArrangedSubview(locationLabel)
addSubview(rearViewImageView)
addSubview(captionLabel)
// constraints
userProfileImageView.snp.makeConstraints { make in
// inset 5-points from top and leading of self
make.top.equalToSuperview().inset(5)
make.leading.equalToSuperview().inset(5)
// width == 50
make.width.equalTo(50)
// height == width
make.height.equalTo(userProfileImageView.snp.width)
}
stackView.snp.makeConstraints { make in
// leading 5-points from profile image view trailing
make.centerY.equalTo(userProfileImageView.snp.centerY)
// centered vertically to profile image view
make.leading.equalTo(userProfileImageView.snp.trailing).offset(5)
}
rearViewImageView.snp.makeConstraints { make in
// top 10-points from profile image view bottom
make.top.equalTo(userProfileImageView.snp.bottom).offset(10)
// "full width"
make.leading.trailing.equalToSuperview()
// height == width * 4/3
make.height.equalTo(rearViewImageView.snp.width).multipliedBy(4.0 / 3.0)
// don't need this
//make.centerX.equalToSuperview()
}
captionLabel.snp.makeConstraints { make in
// top 5-points from rear view image view bottom
make.top.equalTo(rearViewImageView.snp.bottom).offset(5)
// leading == rear view image view leading
make.leading.equalTo(rearViewImageView.snp.leading)
// *probably* also want to constrain the trailing
make.trailing.equalTo(rearViewImageView.snp.trailing)
// bottom 10-points from bottom of self
make.bottom.equalToSuperview().inset(10)
// note: the bottom constraint also sets the Height of self,
// provided we don't constrain it differently "from outside"
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Simple test view controller
class TestUserViewVC: UIViewController {
let testView = UserView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(testView)
let g = view.safeAreaLayoutGuide
testView.snp.makeConstraints { make in
// top, leading, trailing 20-points inset from safe-area
make.top.leading.trailing.equalTo(g).inset(20.0)
// we don't give it a Height constraint, because
// UserView's internal constraints will set the height
}
}
}
Result:
You didn't show setting any stack view properties, but I'm guessing you want the two labels to be vertical with a little "gap":
stackView.axis = .vertical
stackView.spacing = 4
gives us this:
Edit - after OP updated original code...
In the code you left our of your original post, you have this in your PostView
class:
var stackView: UIStackView {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 5
return stackView
}
You're missing the =
on the first line, and the ()
on the last line:
var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 5
return stackView
}()
So, you've created a function that returns a UIStackView
, as opposed to actually creating a UIStackView
Upvotes: 0