Related
I'm using FSCalendar to create a calendar for my app but I can not get the Interactive Scope Gesture to work. I have downloaded the example project and read through the documentation but it still crashes with this error Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value on line calendarHeightConstraint.constant = 350. All I'm trying to achieve is the exact same as the documentation shows with the Interactive Scope Gesture but programmatically.
var calendarHeightConstraint: NSLayoutConstraint!
let calendarView: FSCalendar = {
let cl = FSCalendar()
cl.allowsMultipleSelection = false
return cl
}()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor(named: "backgroundColor")
cv.showsVerticalScrollIndicator = false
cv.showsHorizontalScrollIndicator = false
return cv
}()
fileprivate lazy var scopeGesture: UIPanGestureRecognizer = {
[unowned self] in
let panGesture = UIPanGestureRecognizer(target: self.calendarView, action: #selector(self.calendarView.handleScopeGesture(_:)))
panGesture.delegate = self
panGesture.minimumNumberOfTouches = 1
panGesture.maximumNumberOfTouches = 2
return panGesture
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(named: "backgroundColor")
collectionView.delegate = self
collectionView.dataSource = self
calendarView.delegate = self
calendarView.dataSource = self
calendarHeightConstraint.constant = 350
self.calendarView.select(Date())
self.view.addGestureRecognizer(self.scopeGesture)
self.calendarView.addGestureRecognizer(self.scopeGesture)
self.calendarView.scope = .week
setupLayout()
setupCollectionView()
}
func setupLayout() {
view.addSubview(calendarView)
calendarView.anchor(top: topLayoutGuide.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 75, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: view.frame.width, height: 350)
view.addSubview(collectionView)
collectionView.anchor(top: calendarView.bottomAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 15, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}
func setupCollectionView() {
collectionView.register(CalendarCollectionCell.self, forCellWithReuseIdentifier: cellId)
collectionView.dataSource = self
collectionView.delegate = self
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CalendarCollectionCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 70)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 15
}
func calendar(_ calendar: FSCalendar, boundingRectWillChange bounds: CGRect, animated: Bool) {
self.calendarHeightConstraint.constant = bounds.height
self.view.layoutIfNeeded()
}
You don't init
var calendarHeightConstraint: NSLayoutConstraint!
or link it as an outlet hence it's nil , you need to link it here
calendarView.anchor(top: topLayoutGuide.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 75, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: view.frame.width, height: 350)
so you can play with it's constant as needed , so you can do
NSLayoutConstraint.activate([
// add left , right and top constraints
])
in addition to
calendarHeightConstraint = calendarView.heightAnchor.constraint(equalToConstant: 350)
calendarHeightConstraint.isActive = true
Don't try to use calendarHeightConstraint before the above 2 lines as this will give the nil crash
You can fix this by doing two things.
Remove the line calendarHeightConstraint.constant = 350 from viewDidLoad.
Initialize the calendarHeightConstraint in setupLayout.
Instead of:
calendarView.anchor(top: topLayoutGuide.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 75, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: view.frame.width, height: 350)
Use:
calendarView.anchor(top: topLayoutGuide.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 75, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: view.frame.width, height: 0)
calendarHeightConstraint = calendarView.heightAnchor.constraint(equalToConstant: 350)
calendarHeightConstraint.isActive = true
I'm trying to animate a stackview and its elements upon pressing a UIButton. the problem is that the stackview is created inside a function which is called in viewdidload.
Because the stackview is created inside a function I cannot call it inside the function that manages the animation... I think i'm doing something wrong...
my stackview creation function
fileprivate func variableContent() {
let stackviewButtons = UIStackView(arrangedSubviews: [registerSerialNumberButton, dothislaterButton])
stackviewButtons.axis = .vertical
stackviewButtons.alignment = .fill
stackviewButtons.spacing = 10
let stackview = UIStackView(arrangedSubviews: [registerTitlelabel, registerdescriptionLabel, serialnumberInput, stackviewButtons])
stackview.axis = .vertical
stackview.spacing = 20
stackview.setCustomSpacing(40, after: registerdescriptionLabel)
stackview.setCustomSpacing(100, after: serialnumberInput)
view.addSubview(stackview)
stackview.anchor(top: nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 0)
stackview.centerXInSuperview()
stackview.centerYInSuperview()
}
viewdidload
override func viewDidLoad() {
variableContent()
}
You can return the stackview from your function and keep a reference to it as an instance variable. That way you can reuse it later.
class Controller: UIViewController {
var stackView: UIStackView!
fileprivate func variableContent() -> UIStackView {
let stackviewButtons = UIStackView(arrangedSubviews: [registerSerialNumberButton, dothislaterButton])
stackviewButtons.axis = .vertical
stackviewButtons.alignment = .fill
stackviewButtons.spacing = 10
let stackview = UIStackView(arrangedSubviews: [registerTitlelabel, registerdescriptionLabel, serialnumberInput, stackviewButtons])
stackview.axis = .vertical
stackview.spacing = 20
stackview.setCustomSpacing(40, after: registerdescriptionLabel)
stackview.setCustomSpacing(100, after: serialnumberInput)
view.addSubview(stackview)
stackview.anchor(top: nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 0)
stackview.centerXInSuperview()
stackview.centerYInSuperview()
return stackview
}
override func viewDidLoad() {
super.viewDidLoad()
self.stackView = variableContent()
}
}
An other option would be to give it a tag (stackview.tag = 42)
and recover it with self.view.viewWithTag(42)
In addition to Marcio's answer, you can also declare the stackView optionally and set it in the function:
class Controller: UIViewController {
var stackview: UIStackView?
fileprivate func variableContent() {
let stackviewButtons = UIStackView(arrangedSubviews: [registerSerialNumberButton, dothislaterButton])
stackviewButtons.axis = .vertical
stackviewButtons.alignment = .fill
stackviewButtons.spacing = 10
self.stackview = UIStackView(arrangedSubviews: [registerTitlelabel, registerdescriptionLabel, serialnumberInput, stackviewButtons])
stackview.axis = .vertical
stackview.spacing = 20
stackview.setCustomSpacing(40, after: registerdescriptionLabel)
stackview.setCustomSpacing(100, after: serialnumberInput)
view.addSubview(stackview)
stackview.anchor(top: nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 0)
stackview.centerXInSuperview()
stackview.centerYInSuperview()
}
// Whatever your animating function is:
func animateStackView() {
guard let stackview = self.stackview else { return }
// You now have access to your stackView for the rest of this function.
}
}
Note: For consistency with your question, I kept the name of the UIStackView as stackview in the code, but according to Swift naming conventions, you should name variables in camelcase (stackView)
I've implemented inputAccessoryView with UITextView. I also set collectionView.keyboardDismissMode = .interactive
When i dissmiss keyboard by using interactive mode i have this two error
1) - First responder warning: '<.InputTextView: 0x7f8f54061800; baseClass = UITextView; frame = (8 8; 307 35.5); text = ''; clipsToBounds = YES; gestureRecognizers = ; layer = ; contentOffset: {0, 0}; contentSize: {130, 0}; adjustedContentInset: {0, 0, 0, 0}>' rejected resignFirstResponder when being removed from hierarchy
2) - [UIWindow endDisablingInterfaceAutorotationAnimated:] called on > without matching -beginDisablingInterfaceAutorotation. Ignoring.
Xcode 10 and swift 4
Code For CollectionViewController
lazy var containerView: InputAccesoryView = {
let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 50)
let containerView = InputAccesoryView(frame: frame)
containerView.backgroundColor = UIColor.groupTableViewBackground
containerView.autoresizingMask = .flexibleHeight
containerView.delegate = self
return containerView
}()
override var inputAccessoryView: UIView {
get {
return containerView
}
}
override var canBecomeFirstResponder: Bool {
return true
}
Code for InputAccesoryView
public let inputTextView: InputTextView = {
let textView = InputTextView()
textView.font = UIFont.systemFont(ofSize: 16)
textView.isScrollEnabled = false
textView.textAlignment = .left
textView.layer.cornerRadius = 10
return textView
}()
private lazy var sendButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named: "send2"), for: .normal)
button.tintColor = .black
button.addTarget(self, action: #selector(handleUploadComment), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
configureViewComponents()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return CGSize.zero
}
private func configureViewComponents(){
addSubview(sendButton)
sendButton.setPosition(top: nil, left: nil, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 8, width: 44, height: 0)
sendButton.centerYAnchor.constraint(equalTo: layoutMarginsGuide.centerYAnchor).isActive = true
addSubview(inputTextView)
inputTextView.setPosition(top: topAnchor, left: leftAnchor, bottom: layoutMarginsGuide.bottomAnchor, right: sendButton.leftAnchor, paddingTop: 8, paddingLeft: 8, paddingBottom: 8, paddingRight: 8, width: 0, height: 0)
let separatorView = UIView()
separatorView.backgroundColor = UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1)
addSubview(separatorView)
separatorView.setPosition(top: topAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
}
func clearCommentTextView() {
inputTextView.text = nil
inputTextView.placeHolder.isHidden = false
}
I am trying to retrieve some data that Is stored in firebase, and allow that Information to be displayed inside of a collection view. Such as profileImageUrl, name label and email label.
I have tried to read the documentation on firebase and apple website, but for some reason, my code returns plain. I don't receive any errors, but my code is not running as expected. When I go to my app's profile view, the only texts that are displayed are the placeholder that I programmatically placed.
let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "users")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
return imageView
}()
let nameLabel: UILabel = {
let label = UILabel()
label.text = "User's Name"
label.font = UIFont.boldSystemFont(ofSize: 18)
label.textColor = GREEN_Theme
return label
}()
let uidLabel: UILabel = {
let label = UILabel()
label.text = "User's uid"
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = GREEN_Theme
return label
}()
let emailLabel: UILabel = {
let label = UILabel()
label.text = "User's email"
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = GREEN_Theme
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
func setupView() {
if Auth.auth().currentUser != nil {
guard let uid = Auth.auth().currentUser?.uid else {
return }
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String : Any] else {
return }
let user = CurrentUser(uid: uid, dictionary: dictionary)
self.uidLabel.text = uid
self.nameLabel.text = user.name
self.emailLabel.text = user.email
self.profileImageView.loadImageUsingCacheWithUrlString(user.profileImageUrl)
}, withCancel: { (err) in
print("attempting to load information")
})
self.addSubview(profileImageView)
self.addSubview(nameLabel)
self.addSubview(emailLabel)
self.addSubview(uidLabel)
profileImageView.anchors(top: topAnchor, topPad: 125, bottom: bottomAnchor, bottomPad: 75, left: leftAnchor, leftPad: 20, right: rightAnchor, rightPad: 250, height: 20, width: 20)
nameLabel.anchors(top: profileImageView.bottomAnchor, topPad: -50, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 0, height: 20, width: 20)
emailLabel.anchors(top: nameLabel.bottomAnchor, topPad: -80, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 125, height: 20, width: 20)
uidLabel.anchors(top: emailLabel.bottomAnchor, topPad: -40, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 0, height: 20, width: 20)
profileImageView.layer.zPosition = 10
nameLabel.layer.zPosition = 10
emailLabel.layer.zPosition = 10
profileImageView.layer.borderWidth = 2.0
profileImageView.layer.cornerRadius = 50
profileImageView.layer.borderWidth = 2.0
profileImageView.layer.borderColor = UIColor.white.cgColor
profileImageView.layer.masksToBounds = true
}
}
I would really appreciate it if someone could help critique what I have, as well as help show me what the correct implementation would look like. Any and all help would be greatly appreciated
There are several things that are not correct.
your cell should not be the one in charge of downloading or doing any network call, leave that to the viewController or even better a separate handler for all the network calls.
Firebase is asynchronous so by the time your data is downloaded all the views have already been set and displayed, that is why you are only seeing your placeholders.
You have a lot of redundant code for checking optionals, this is not much of a problem but it is not needed.
You should really think about changing all this code and separating the different tasks to different handlers. For example, you could create a separate class that controls all the calls to Firebase and returns the values with completion blocks, from the viewController or the collectionView itself ask the network handler to give you the data that you need, pass this data to the cell and let the cell display the data, the cell should only display the data, not download it or ask for networks requests. I cannot write up a complete example for you but I think that if you go around in google you will find a lot of very good examples and tutorials.
Anyway, just to get you started and so you have a look at the mistakes that you have in your code, here is a quick fix for it. Even if it works, don't take for granted that everything is fixed. You should really refactor all the code and separate the different tasks. Hope this helps at least so you can solve the problem of displaying your downloaded data in the cell.
let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "users")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
return imageView
}()
let nameLabel: UILabel = {
let label = UILabel()
label.text = "User's Name"
label.font = UIFont.boldSystemFont(ofSize: 18)
label.textColor = GREEN_Theme
return label
}()
let uidLabel: UILabel = {
let label = UILabel()
label.text = "User's uid"
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = GREEN_Theme
return label
}()
let emailLabel: UILabel = {
let label = UILabel()
label.text = "User's email"
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = GREEN_Theme
return label
}()
// Here you add a variable that is observed for SET, once a set happens bind() will be called and the views will be updated.
var currentUser : CurrentUser? {
didSet {
self.bind()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
//...
}
// setupView() should only add the UI appearance, and in this case it is calling firebase and setting currentUser, but it should not do that. This is what you have to refactor.
func setupView() {
self.addSubview(profileImageView)
self.addSubview(nameLabel)
self.addSubview(emailLabel)
self.addSubview(uidLabel)
profileImageView.anchors(top: topAnchor, topPad: 125, bottom: bottomAnchor, bottomPad: 75, left: leftAnchor, leftPad: 20, right: rightAnchor, rightPad: 250, height: 20, width: 20)
nameLabel.anchors(top: profileImageView.bottomAnchor, topPad: -50, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 0, height: 20, width: 20)
emailLabel.anchors(top: nameLabel.bottomAnchor, topPad: -80, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 125, height: 20, width: 20)
uidLabel.anchors(top: emailLabel.bottomAnchor, topPad: -40, bottom: bottomAnchor, bottomPad: 0, left: profileImageView.leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 0, height: 20, width: 20)
profileImageView.layer.zPosition = 10
nameLabel.layer.zPosition = 10
emailLabel.layer.zPosition = 10
profileImageView.layer.borderWidth = 2.0
profileImageView.layer.cornerRadius = 50
profileImageView.layer.borderWidth = 2.0
profileImageView.layer.borderColor = UIColor.white.cgColor
profileImageView.layer.masksToBounds = true
guard let user = Auth.auth().currentUser else { return }
let uid = user.uid
let reference = Database.database().reference().child("users").child(uid)
reference.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String : Any] else { return }
self.currentUser = CurrentUser(uid: uid, dictionary: dictionary)
}, withCancel: { (err) in
print("attempting to load information")
})
}
// This is the method that updates the cell views.
func bind() {
self.uidLabel.text = currentUser.uid
self.nameLabel.text = currentUser.name
self.emailLabel.text = currentUser.email
self.profileImageView.loadImageUsingCacheWithUrlString(currentUser.profileImageUrl)
}
I have been trying to programmatically create two separate view controllers in which I have a login, and sign up view controller. While my initial view controller, Login allows me to see the email/password text field, my sign up controller does not. I have plugged in the correct information however when I run my application, it is blank. I will post my code below, can anyone help me solve this issue?
let emailTextField: UITextField = {
let e = UITextField()
let attributedPlaceholder = NSAttributedString(string: "Email", attributes:
[NSAttributedStringKey.foregroundColor : UIColor.white])
e.textColor = .white
e.attributedPlaceholder = attributedPlaceholder
e.setBottomBorder(backGroundColor: GREEN_Theme, borderColor: .white)
return e
}()
let passwordTextField: UITextField = {
let p = UITextField()
let attributedPlaceholder = NSAttributedString(string: "Password", attributes: [NSAttributedStringKey.foregroundColor : UIColor.white])
p.textColor = .white
p.isSecureTextEntry = true
p.attributedPlaceholder = attributedPlaceholder
p.setBottomBorder(backGroundColor: GREEN_Theme, borderColor: .white)
return p
}()
override func viewDidLoad (){
super.viewDidLoad()
view.backgroundColor = GREEN_Theme
func setupFileComponents() {
setupEmailField()
setupPasswordField()
}
func setupEmailField() {
view.addSubview(emailTextField)
emailTextField.anchors(top: nil, topPad: 0, bottom: nil, bottomPad: 0, left: view.leftAnchor, leftPad: 24, right: view.rightAnchor, rightPad: 24, height: 30, width: 0)
emailTextField.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
func setupPasswordField() {
view.addSubview(passwordTextField)
passwordTextField.anchors(top: emailTextField.bottomAnchor, topPad: 8, bottom: nil, bottomPad: 0, left: emailTextField.leftAnchor, leftPad: 0, right: emailTextField.rightAnchor, rightPad: 0, height: 30, width: 0)
}
Thank you in advance.
You should move setupFileComponents, setupEmailField, setupPasswordField out of viewDidLoad then you have to add te calls in viewDidLoad like that:
override func viewDidLoad (){
super.viewDidLoad()
view.backgroundColor = GREEN_Theme
setupFileComponents()
setupEmailField()
setupPasswordField()
}