Alright, I'm having some issues in Swift 3 while trying desperately to add an MSStickerBrowserView to a UIScrollview.
I'm working in a message extension app and have added my scrollview via storyboard (I usually make them programmatically but I thought it'd be easier with constraints). I have connected the scrollview as an outlet in my MessagesViewController script.
I need to add an MSStickerBrowser I have created as a subview of this scrollview, meaning the user would be able to scroll TO the browser view and then scroll through the actual browser.
This is how I create my browser view normally and add it to the view - when added to the view it all works as it should:
func createStickerBrowser() {
let controller = MSStickerBrowserViewController(stickerSize: .large)
addChildViewController(controller)
view.addSubview(controller.view)
controller.stickerBrowserView.backgroundColor = UIColor.gray
controller.stickerBrowserView.dataSource = self
//resize this programmatically later
view.topAnchor.constraint(equalTo: controller.view.topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: controller.view.bottomAnchor).isActive = true
view.leftAnchor.constraint(equalTo: controller.view.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: controller.view.rightAnchor).isActive = true
}
I don't know a lot about MSStickerBrowsers because the tutorials have been limited, however I know this ties the browser's size to that of the changing Message VC and adds it as a normal subview.
I then tried logically to just add it as a subview of the scrollview instead:
let controller = MSStickerBrowserViewController(stickerSize: .large)
//controller.view.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height * 0.5)
addChildViewController(controller)
scrollView.addSubview(controller.view)
controller.stickerBrowserView.backgroundColor = UIColor.gray
controller.stickerBrowserView.dataSource = self
//resize this programmatically later
scrollView.topAnchor.constraint(equalTo: controller.view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: controller.view.bottomAnchor).isActive = true
scrollView.leftAnchor.constraint(equalTo: controller.view.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: controller.view.rightAnchor).isActive = true
I've tried several variations but no matter what the sticker browser view is nowhere. The scrollview is there, not the browser, and no errors on that are printed to the console.
This seems like it should be simple - what am I doing wrong?
Related
I am trying to build a imageview with a button that attached to itself for settings page. I want to tell users to click to image if they want to change it. So what I wanna to do is similar to : this
I was designin my app with storyboard but I couldn't find a way to do it.
So I tried to set constraints programmatically.
For example,
`
editButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
editButton.trailingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: -66),
editButton.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: -60)
])
`
This constraints works for iPad screen but not for any iPhones. There must be another way to design such things. How can I do it?
All of this can be done with storyboards or using constraints in code. Should be the same for iPhone or iPad. For example:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let myImageView = UIImageView()
let myImageViewCornerRadius: CGFloat = 40.0
// Just using a color for the example...
myImageView.backgroundColor = .blue
myImageView.layer.cornerRadius = myImageViewCornerRadius
myImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myImageView)
let myEditButton = UIButton()
// Again using a color...
myEditButton.backgroundColor = .red
myEditButton.translatesAutoresizingMaskIntoConstraints = false
myEditButton.layer.cornerRadius = 10.0
view.addSubview(myEditButton)
NSLayoutConstraint.activate([
myImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
myImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
myImageView.widthAnchor.constraint(equalToConstant: 80.0),
myImageView.heightAnchor.constraint(equalToConstant: 80.0),
myEditButton.centerYAnchor.constraint(equalTo: myImageView.bottomAnchor,
constant: -myImageViewCornerRadius / 4.0),
myEditButton.centerXAnchor.constraint(equalTo: myImageView.trailingAnchor,
constant: -myImageViewCornerRadius / 4.0),
myEditButton.widthAnchor.constraint(equalToConstant: 20.0),
myEditButton.heightAnchor.constraint(equalToConstant: 20.0)
])
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
I would make the origin of the frame of the button reference the frame of the profile photo. So the button is rendered in the same place relative to the photo no matter the size of the photo. Maybe something like this:
editButton.frame = CGRect(x: profileImageView.width - 60, y: profileImageView.height - 66, width: 50, height: 50)
You could also consider adding a tapGestureRecognizer to the photo to trigger the edit option.
I am trying to place a UIPickerView on top of my TableView when a button is clicked. I tried using the storyboard, but it moves the rows of the table down. What I want is the picker to appear as a popover when the button is clicked, then disappear when an option is selected.
Here is the code I am using to get the picker to display:
let picker = UIPickerView()
let container = UILayoutGuide()
view.addLayoutGuide(container)
picker.backgroundColor = UIColor.white
picker.dataSource = self
picker.delegate = self
view.addSubview(picker)
picker.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
picker.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
picker.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
picker.heightAnchor.constraint(equalToConstant: 100).isActive = true
picker.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
However, the picker shows up like this:
I don't understand why the picker view is not stretched until the end because I am setting the trailingAnchor as the parent view's trailingAnchor.
Have you set translatesAutoresizingMaskIntoConstraints of your picker view to false? It ignores constraints unless you do that
If trailing anchor is not working, then try setting left, top and right to 0 and provide a height as well (if you don't wish to cover the complete tableView) .
Also, add your pickerView on top of your tableView for these
constraints to be applied
If you wish to do it programatically, you can provide the constraints while you're creating the UIPickerView object
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 50))
Instead of the layout guide, you can directly use your table view as of your relative view.
Coding Example:
func addPickerView() {
let picker = UIPickerView()
// let container = UILayoutGuide()
// view.addLayoutGuide(container)
picker.translatesAutoresizingMaskIntoConstraints = false
picker.backgroundColor = UIColor.red
picker.dataSource = self
picker.delegate = self
view.addSubview(picker)
picker.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
picker.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
picker.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
picker.heightAnchor.constraint(equalToConstant: 100).isActive = true
picker.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
}
Above code produces the following output which is stretched to its parent view.
Set constraints equal to parent view then show perfect.
let topBarHeight = UIApplication.shared.statusBarFrame.size.height +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
let picker = UIPickerView()
picker.translatesAutoresizingMaskIntoConstraints = false
let container = UILayoutGuide()
view.addLayoutGuide(container)
picker.backgroundColor = UIColor.lightGray
picker.dataSource = self
picker.delegate = self
view.addSubview(picker)
picker.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
picker.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
picker.topAnchor.constraint(equalTo: view.topAnchor, constant: topBarHeight).isActive = true
picker.heightAnchor.constraint(equalToConstant: 200).isActive = true
picker.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
I figured out the best way to do it, in the Interface Builder itself. I had to change the TableViewController to a ViewController and put the TableView inside of it (setting the ViewController as a TableViewDelegate and TableViewDataSource). This allowed me to place the PickerView on the IB, as a layered view on top of my table cells. Then I could hide and show it as I pleased by creating an IBOutlet for it and setting the .isHidden property appropriately.
More info here: Swift: TableView in ViewController
I have a UIScrollView which is a dashboard basically with relevant information for the user. It has a pager underneath and contains three UIViews at the moment.
I used to set the different views with a CGRect as the frame, setting the x offset to the width of the UIScrollView * the page number. This worked fine, but it was a hassle to make the UIScrollView disappear when scrolling in the UITableView underneath.
I am now using AutoLayout with constraints to display the information, which works fine. However, my UIScrollView is not scrolling anymore. It does not respond to any swipe gestures I perform. However, it does respond to taps in the pager, which shows me the offsets and constraints are right, I am just unable to scroll in it. Take a look:
My UIScrollView is made up like this:
var dashboardView: UIScrollView = {
let dashboardView = UIScrollView()
dashboardView.translatesAutoresizingMaskIntoConstraints = false
dashboardView.backgroundColor = UIColor.clear
dashboardView.layer.masksToBounds = true
dashboardView.layer.cornerRadius = 10
dashboardView.isPagingEnabled = true
dashboardView.showsHorizontalScrollIndicator = false
dashboardView.showsVerticalScrollIndicator = false
dashboardView.alwaysBounceHorizontal = false
dashboardView.alwaysBounceVertical = false
return dashboardView
}()
I then set the different views like so:
for index in 0..<3 {
let currentDash = UIView()
currentDash.backgroundColor = UIColor.lightGray
currentDash.layer.cornerRadius = 10
currentDash.layer.masksToBounds = false
currentDash.translatesAutoresizingMaskIntoConstraints = false
currentDash.isUserInteractionEnabled = false
dashboardView.addSubview(currentDash)
currentDash.topAnchor.constraint(equalTo: dashboardView.topAnchor).isActive = true
currentDash.bottomAnchor.constraint(equalTo: dashboardView.bottomAnchor).isActive = true
currentDash.leadingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: dashboardWidth * CGFloat(index)).isActive = true
currentDash.trailingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: (dashboardWidth * CGFloat(index)) + dashboardWidth).isActive = true
currentDash.widthAnchor.constraint(equalToConstant: dashboardWidth).isActive = true
currentDash.heightAnchor.constraint(equalToConstant: dashboardHeight).isActive = true
}
When I remove the constraints (AutoLayout) and set it with frames it works perfectly, like this:
dashFrame.origin.x = dashboardWidth * CGFloat(index)
dashFrame.size = CGSize(width: dashboardWidth, height: dashboardHeight)
let currentDash = UIView(frame: dashFrame)
But then, because of setting the frame, the view doesn't scroll up as I would, which is why I want to use AutoLayout.
Any ideas or suggestions as to what I am doing wrong? My ScrollViewDidScroll() method simply gets not called, because a simple print doesn't return anything in my console.
I have tried setting the isUserInteractionEnabled property to false on the currentDash UIView, which was no success. I have also tried removing all constraints separately, some together, etc. - but also this doesn't work - I need a topAnchor, bottomAnchor, heightAnchor and widthAnchor at least.
Oh, and, of course, I set the contentSize property:
dashboardView.contentSize = CGSize(width: dashboardWidth * CGFloat(dashboardPager.numberOfPages), height: dashboardHeight)
Any suggestions or hints towards the right answer would be deeply appreciated. Thanks!
EDIT: I also set constraints for the UIScrollView itself:
dashboardView.topAnchor.constraint(equalTo: dashboardBG.topAnchor).isActive = true
dashboardView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: normalSpacing).isActive = true
dashboardView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -normalSpacing).isActive = true
dashboardView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
dashboardView.heightAnchor.constraint(equalToConstant: dashboardHeight).isActive = true
dashboardView.delegate = self
The issue is here
currentDash.leadingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: dashboardWidth * CGFloat(index)).isActive = true
currentDash.trailingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: (dashboardWidth * CGFloat(index)) + dashboardWidth).isActive = true
1- Regarding leading of the views , the leading of the first view should be pinned to the scrollView , the leading of the second view pinned to the trailing of the previous and so on , so
if index == 0{
// make leading with scrolview leading
else
{
// make leading with prevView trailing
}
So make a var initiated with scrollView and change end of for loop
2- Regarding the trailing , only the last view trailing be pinned to the trailing of the scrollview
if index == 2 { // last
// make trailing with scrollView
}
I eventually figured it out thanks to Sh_Khan’s answer. I held on to the original code, but removed the trailingAnchor for all indexes and added it only to the last index.
for index in 0..<3 {
let currentDash = UIView()
currentDash.backgroundColor = UIColor.lightGray
currentDash.layer.cornerRadius = 10
currentDash.layer.masksToBounds = false
currentDash.translatesAutoresizingMaskIntoConstraints = false
currentDash.isUserInteractionEnabled = false
dashboardView.addSubview(currentDash)
currentDash.topAnchor.constraint(equalTo: dashboardView.topAnchor).isActive = true
currentDash.bottomAnchor.constraint(equalTo: dashboardView.bottomAnchor).isActive = true
currentDash.leadingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: dashboardWidth * CGFloat(index)).isActive = true
currentDash.widthAnchor.constraint(equalToConstant: dashboardWidth).isActive = true
currentDash.heightAnchor.constraint(equalToConstant: dashboardHeight).isActive = true
if index == 2 {
currentDash.trailingAnchor.constraint(equalTo: dashboardView.trailingAnchor).isActive = true
}
}
Thanks for the help!
I have iOS app where I have a couple views with a shadow (Called them shadowView for convenience). This is how they're made:
let shadowView = UIView(frame: .zero)
self.addSubview(shadowView)
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0.5, height: 1.5)
shadowView.layer.shadowOpacity = 0.15
shadowView.layer.masksToBounds = false
shadowView.layer.cornerRadius = 8
shadowView.backgroundColor = .clear
shadowView.layer.shadowRadius = 5.0
shadowView.clipsToBounds = false
shadowView.layer.shadowPath = UIBezierPath(roundedRect: shadowView.bounds, cornerRadius: 20).cgPath
The view is then placed in position with Snapkit, but I left out that part of the code due to it being irrelevant. Now the shadow that I made here is not displayed. However, if I set the backgroundColor property to e.g. UIColor.yellow then the view itself shows, but not the shadow.
I also checked if the shadow might be cut off by any parent view, which doesn't seem to be the case.
As you can see it's not the usual clipsToBounds / masksToBounds mistake, and I've been looking at this for the last couple hours. Am I missing a piece of code maybe? Or did I miss anything?
Your frame size is .zero. Assign some valid size to it.
Give the view a non-zero frame size
let viewWidth = //any
let viewHeight = //any
let shadowView = UIView(frame: CGRect.init(x: 0, y: 0, width: viewWidth, height: viewHeight))
To try and clarify...
You are setting .shadowPath, but based on you code you're setting it prior to .shadowView having its frame set. So, you are creating a UIBezierPath with a frame of .zero -- and it never changes.
You need to set the shadow path either in viewDidLayoutSubviews of a view controller, or by overriding layoutSubviews() inside your custom view.
Here is a simple example that you can run in a Playground page:
import UIKit
import PlaygroundSupport
class TestViewController : UIViewController {
let shadowView = UIView(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(shadowView)
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOpacity = 0.15
shadowView.layer.cornerRadius = 8
shadowView.backgroundColor = .clear
// this would be handled by your use of SnapKit
shadowView.translatesAutoresizingMaskIntoConstraints = false
shadowView.topAnchor.constraint(equalTo: view.topAnchor, constant: 40.0).isActive = true
shadowView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0).isActive = true
shadowView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0).isActive = true
shadowView.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
shadowView.layer.shadowPath = UIBezierPath(roundedRect: shadowView.bounds, cornerRadius: 20).cgPath
}
}
let vc = TestViewController()
vc.view.backgroundColor = .white
PlaygroundPage.current.liveView = vc
Like other colleagues said, the frame of your view is the problem.
This is a problem just because you set the layer's shadow path to be as big as the view's bounds that in that moment is .zero.
Still I understand that you want your view to react to its constraint when the auto-layout engine is ready to layout your views. There are then two things you can do to address this problem:
1) you can remove the explicit set of shadowView.layer.shadowPath and change the color of the view from clear to another color. This will cause the shadow to follow the shape of the view even if it changes, with a dynamic shadow path. While this will fix the issue, sometimes it results also in an impact on performance during animations and transitions.
2) if the view doesn't change its size during her life on screen you can force the superView to re-layout itself right after you add the shadowView as subview.
self.addSubview(shadowView)
self.setNeedsLayout()
self.layoutIfNeeded()
In this way when you'll create the shadow path bounds will not be zero because auto layout already did its magic changing the frame of your view.
Please note that with the second solution you will not see the view (that has clear color) but just its shadow.
Swift 4
Add this function in your view controller
func addShadow(myView: UIView?) {
myView?.layer.shadowColor = UIColor.black.cgColor
myView?.layer.shadowOffset = CGSize(width: 0, height:0)
myView?.layer.shadowOpacity = 0.3
myView?.layer.shadowRadius = 5
myView?.layer.masksToBounds = false
myView?.layer.cornerRadius = 12
}
and use it like:
let shadowView = UIView(frame: CGRect(x: 0, y: 0, width: yourWidth, height: yourHeight))
self.addShadow(shadowView)
I want to make a UIView stick on the bottom while I am scrolling in my UITableView.
My idea was to set a UIView with the position of right above the navigation item. Set it's zPosition to 1.
The problem is, that the yPosition of my UITableView varies.
Any idea how to solve this?
Edit:
Providing Screenshots for visible vs. expected behaviour:
Visible:
This is when I scroll:
Expected:
As seen on Tinder Camera Symbol above Table:
Edit2:
This code is what I use to put the rectangle to the bottom.
It works until I swipe the UITableView - The rectangle also scrolls up.
let bounds = self.view.bounds
let yPosition = self.navigationController?.toolbar.frame.minY
print(yPosition)
let myView = UIView(frame: CGRect(x: 0, y: yPosition! - bounds.height/6, width: bounds.width, height: bounds.height/6))
myView.backgroundColor = myColor.rookie
myView.alpha = 0.8
myView.layer.zPosition = 1
myView.isUserInteractionEnabled = true
self.view.addSubview(myView)
there is a solution for this. you can do this by disabling the Auto Layout(button.translatesAutoresizingMaskIntoConstraints = false) property of the corresponding Button or any UIView for floating button:
Swift 4
button.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
button.rightAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
} else {
button.rightAnchor.constraint(equalTo: tableView.layoutMarginsGuide.rightAnchor, constant: 0).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.layoutMarginsGuide.bottomAnchor, constant: -10).isActive = true
}