Constraint animation with SnapKit - ios

I am trying to implement the animation of 2 views using SnapKit.
Here is my Animation view:
class MatchAnimation: UIView {
let viewBackground: UIView = {
let view = UIView()
view.backgroundColor = UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.75)
view.alpha = 1
return view
}()
let matchView: UIView = {
let view = UIView()
return view
}()
let matchLabel: UILabel = {
let label = UILabel()
label.text = "Title"
label.textColor = .white
label.textAlignment = .center
return label
}()
let leftAvatarBg: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 91/2
return view
}()
let rightAvatarBg: UIView = {
let view = UIView()
view.backgroundColor = .blue
view.layer.cornerRadius = 91/2
return view
}()
let goToChatButton: UIButton = {
let button = UIButton()
button.setTitle("Button", for: .normal)
button.backgroundColor = .red
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 24.5
return button
}()
init() {
super.init(frame: UIScreen.main.bounds)
viewBackground.frame = self.frame
self.addSubview(viewBackground)
self.addSubview(matchView)
matchView.addSubview(matchLabel)
matchView.addSubview(leftAvatarBg)
matchView.addSubview(rightAvatarBg)
matchView.addSubview(goToChatButton)
matchView.snp.makeConstraints { (make) in
make.left.right.equalToSuperview()
make.center.equalToSuperview()
}
matchLabel.snp.makeConstraints { (make) in
make.top.equalToSuperview()
make.centerX.equalToSuperview()
make.size.equalTo(CGSize(width: 193, height: 40))
}
leftAvatarBg.snp.makeConstraints { (make) in
make.top.equalTo(matchLabel.snp.bottom).offset(20)
make.size.equalTo(CGSize(width: 91, height: 91))
make.right.equalTo(self.snp.left).offset(0)
}
rightAvatarBg.snp.makeConstraints { (make) in
make.top.equalTo(leftAvatarBg)
make.size.equalTo(leftAvatarBg)
make.left.equalTo(self.snp.right).inset(0)
}
goToChatButton.snp.makeConstraints { (make) in
make.size.equalTo(CGSize(width: 171, height: 50))
make.top.equalTo(leftAvatarBg.snp.bottom).offset(25)
make.centerX.equalToSuperview()
make.bottom.equalToSuperview()
}
}
func animate() {
UIView.animate(withDuration: 5) {
self.leftAvatarBg.snp.updateConstraints { (make) in
make.right.equalTo(self.snp.left).offset(UIScreen.main.bounds.width/2+30)
}
self.rightAvatarBg.snp.updateConstraints { (make) in
make.left.equalTo(self.snp.right).inset(UIScreen.main.bounds.width/2+30)
}
self.layoutIfNeeded()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The 2 views I tried to animate are leftAvatarBg and rightAvatarBg.
Before the animation, I set them at the exterior of the screen and want to make them slide from left to right for one view and from right to left for the other.
In my controller, I just call:
func setupAnimation() {
let matchView = MatchAnimation()
view.addSubview(matchView)
matchView.animate()
}
The result of this is that the entire view is animating (scaling).
Did I miss something?
UPDATE: Thanks to swift2geek, it seems that they is a conflict between the creation of the object and the animation. In his solution, he trigger the animation by pressing a button. In my case, I want to trigger the animation as soon as possible and automatically. How can I ensure that the animation will be fired after the creation of the object ?

im not good with your bdsm SnapKit, so put the right constraints on your own. So the main reason its not working - you should separate animation and object creation.
your viewcontroller:
import UIKit
class ViewController: UIViewController {
#IBOutlet var matchButton: MatchButton!
#IBOutlet var matchView: MatchAnimation!
override func viewDidLoad() {
super.viewDidLoad()
setupAnimation()
setupButton()
}
func setupAnimation() {
matchView = MatchAnimation()
matchView.isUserInteractionEnabled = true
view.addSubview(matchView)
}
func setupButton() {
matchButton = MatchButton()
matchButton.isUserInteractionEnabled = true
matchButton.isEnabled = true
matchButton.addTarget(self, action: #selector(pressed(_:)), for: .touchUpInside)
matchView.addSubview(matchButton)
}
#objc func pressed(_ sender: MatchButton!) {
print("button tapped")
matchView.animate()
}
}
your MatchAnimation class:
import UIKit
import SnapKit
class MatchAnimation: UIView {
let viewBackground: UIView = {
let view = UIView()
view.backgroundColor = UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.75)
view.alpha = 1
return view
}()
let matchView: UIView = {
let view = UIView()
return view
}()
let matchLabel: UILabel = {
let label = UILabel()
label.text = "Title"
label.textColor = .white
label.textAlignment = .center
return label
}()
let leftAvatarBg: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 91/2
return view
}()
let rightAvatarBg: UIView = {
let view = UIView()
view.backgroundColor = .blue
view.layer.cornerRadius = 91/2
return view
}()
init() {
super.init(frame: UIScreen.main.bounds)
viewBackground.frame = self.frame
self.addSubview(viewBackground)
self.addSubview(matchView)
matchView.addSubview(matchLabel)
matchView.addSubview(leftAvatarBg)
matchView.addSubview(rightAvatarBg)
matchView.snp.makeConstraints { (make) in
make.left.right.equalToSuperview()
make.center.equalToSuperview()
}
matchLabel.snp.makeConstraints { (make) in
make.top.equalToSuperview()
make.centerX.equalToSuperview()
make.size.equalTo(CGSize(width: 193, height: 40))
}
leftAvatarBg.snp.makeConstraints { (make) in
make.top.equalTo(matchLabel.snp.bottom).offset(20)
make.centerX.equalToSuperview()
make.size.equalTo(CGSize(width: 91, height: 91))
make.right.equalTo(self.snp.left).offset(90)
}
rightAvatarBg.snp.makeConstraints { (make) in
make.top.equalTo(leftAvatarBg)
make.size.equalTo(leftAvatarBg)
make.left.equalTo(self.snp.right).inset(120)
}
}
func animate() {
UIView.animate(withDuration: 5) {
self.leftAvatarBg.snp.updateConstraints { (make) in
make.right.equalTo(self.snp.left).offset(UIScreen.main.bounds.width/2+30)
}
self.rightAvatarBg.snp.updateConstraints { (make) in
make.left.equalTo(self.snp.right).inset(UIScreen.main.bounds.width/2+30)
}
self.layoutIfNeeded()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
and MatchButton:
import UIKit
import SnapKit
class MatchButton: UIButton {
let goToChatButton: UIButton = {
let button = UIButton()
button.setTitle("Button", for: .normal)
button.backgroundColor = .red
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 24.5
return button
}()
init() {
super.init(frame: UIScreen.main.bounds)
self.addSubview(goToChatButton)
goToChatButton.snp.makeConstraints { (make) in
make.size.equalTo(CGSize(width: 171, height: 50))
make.top.greaterThanOrEqualTo(100)
make.centerX.equalToSuperview()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

If you want your animation to fire as soon as possible, just add layout animate() function to viewDidAppear.

Related

Button in UIStackView not clickable

I'm trying to add a button to my stack view. The button has a buttonTapped method that should be called when it is tapped. The problem is it is never being called, the button does not seem to be clickable.
class CustomButton: UIViewController {
var buttonDelegate: ButtonDelegate?
let button = UIButton(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width - 40, height: 30))
init(label: String) {
button.setTitle(label, for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .systemBlue
super.init(nibName: nil, bundle: nil)
}
#objc func buttonTapped() {
print("this never gets printed")
buttonDelegate?.buttonTapped(buttonType: .submit)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
view.addSubview(button)
}
}
And then my main view controller:
protocol ButtonDelegate {
func buttonTapped(buttonType: ButtonType)
}
class DynamicViewController: UIViewController, ButtonDelegate {
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
lazy var contentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private func setupViews() {
view.addSubview(scrollView)
scrollView.addSubview(contentView)
contentView.addSubview(stackView)
let btn = CustomButton(label: "hi")
btn.buttonDelegate = self
self.stackView.addArrangedSubview(btn.view)
}
func buttonTapped(buttonType: ButtonType) {
print("also never gets printed")
}
}
There is nothing overlapping the button or anything like that:
My question is why the button is not clickable.
You are adding the view controller as a subview. So you also need to add as a child.
Add bellow code after self.stackView.addArrangedSubview(btn.view) this line.
self.addChild(btn)
btn.didMove(toParent: self)

Force label to display it's text

I want to include a voting function similar to the reddit app. I think a UIStackView would be perfect for this. Now I'm struggling to make the label between the vote-up and vote-down button display it's text.
I've tried to change the contentCompression to .fittingSizeLevel and to .defaultHigh but this seems to change nothing. On the image you can see, there would be plenty of space to fit the whole text, but it doesn't. What aspect am I missing?
class VotingStackView: UIStackView {
let arrowUpButton: UIButton = {
let button = UIButton()
button.backgroundColor = .clear
button.tintColor = GSSettings.UI.Colors.tintColor
button.imageView?.contentMode = .scaleAspectFit
button.contentEdgeInsets = UIEdgeInsets(top: 16, left: 0, bottom: 16, right: 0)
button.clipsToBounds = true
return button
}()
let arrowDownButton: UIButton = {
let button = UIButton()
button.backgroundColor = .clear
button.tintColor = GSSettings.UI.Colors.tintColor
button.imageView?.contentMode = .scaleAspectFit
button.contentEdgeInsets = UIEdgeInsets(top: 16, left: 0, bottom: 16, right: 0)
button.clipsToBounds = true
return button
}()
let percentageLabel: UILabel = {
let label = UILabel()
label.text = "100%"
label.textColor = GSSettings.UI.Colors.regularTextColor
label.font = GSSettings.UI.Fonts.helveticaLight?.withSize(20)
label.clipsToBounds = false
label.setContentCompressionResistancePriority(UILayoutPriority.fittingSizeLevel, for: .horizontal)
return label
}()
var views: [UIView] = [UIView]()
//MARK: - Init & View Loading
override init(frame: CGRect) {
super.init(frame: frame)
views = [arrowUpButton, percentageLabel ,arrowDownButton]
setupStackView()
setupImages()
setupSubviews()
}
//MARK: - Setup
func setupStackView() {
self.axis = .horizontal
self.spacing = 0
self.alignment = .center
self.distribution = .fillEqually
}
func setupImages() {
let upImage = UIImage(named: "arrow_up")
let downImage = UIImage(named: "arrow_down")
arrowUpButton.setImage(upImage, for: .normal)
arrowDownButton.setImage(downImage, for: .normal)
}
func setupSubviews() {
for view in views {
addArrangedSubview(view)
}
layoutIfNeeded()
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The votingStackView is part of another StackView:
class BottomStackView: UIStackView {
let votingStackView: VotingStackView = {
let stackview = VotingStackView()
return stackview
}()
let addFriendButton: UIButton = {
let button = UIButton()
button.backgroundColor = .clear
button.tintColor = GSSettings.UI.Colors.tintColor
button.setImage(UIImage(named: "plus")?.withRenderingMode(.alwaysTemplate), for: .normal)
return button
}()
let redView: UIView = {
let view = UIView()
view.backgroundColor = .red
return view
}()
var views: [UIView] = [UIView]()
//MARK: - Init & View Loading
override init(frame: CGRect) {
super.init(frame: frame)
views = [votingStackView, addFriendButton, redView]
setupStackView()
setupSubviews()
}
//MARK: - Setup
func setupStackView() {
self.axis = .horizontal
self.spacing = 0
self.alignment = .leading
self.distribution = .fillEqually
}
func setupSubviews() {
for view in views {
addArrangedSubview(view)
}
layoutIfNeeded()
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Change
label.setContentCompressionResistancePriority(UILayoutPriority.fittingSizeLevel, for: .horizontal)
to
label.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: .horizontal)
From the docs it seems that fittingSizeLevel does not take into account parent constraints, so using defaultHigh seems like the safer option.

how to prevent zooming of parantView while zooming of imageView

i have created CustomeView that contain Scroll-view.inside scroll view there is one container view that contain image view plus two button(Okay and cancel).
Following is my view-hierarchy.
CustomeView -> ScrollView -> ContainerView -> (imageView + OtherComponent).
There are two problem i faced.
while zoom in-out imageview,CustomeView is also zoomed in-out with
respect to Scrollview.
other component postion is changed while zoom in-out.
class cameraPreview : UIView , UIScrollViewDelegate {
var selectedImage : UIImage!
var backGroundView = UIView()
var imageScrollview = UIScrollView()
var metaData : [String:Any]?
let backgroundImageView = UIImageView()
var closeButton : UIButton = {
let button = UIButton(type: UIButtonType.custom)
button.setImage(UIImage(named:"closeWhite"), for: .normal)
button.addTarget(self, action: #selector(closeClick), for: .touchUpInside)
return button
}()
var okButton : UIButton = {
let button = UIButton()
button.setTitle("OK", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 15)
button.setTitleColor(UIColor.black, for: .normal)
button.backgroundColor = UIColor.white
button.layer.cornerRadius = 15
button.addTarget(self, action: #selector(okClick), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
commoninit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commoninit()
}
required init(image:UIImage,frame:CGRect,metaData:[String:Any]?) {
super.init(frame: frame)
self.selectedImage = image
self.metaData = metaData
commoninit()
}
override func layoutSubviews() {
}
func commoninit() {
backGroundView.backgroundColor = UIColor.white
backgroundImageView.contentMode = .scaleAspectFit
self.addSubview(imageScrollview)
imageScrollview.addSubview(backGroundView)
backGroundView.addSubview(backgroundImageView)
backGroundView.addSubview(closeButton)
backGroundView.addSubview(okButton)
imageScrollview.snp.makeConstraints { (make) in
make.edges.equalTo(self)
}
backGroundView.snp.makeConstraints { (make) in
make.edges.equalTo(imageScrollview)
make.height.width.equalTo(self)
}
backgroundImageView.snp.makeConstraints { (make) in
make.edges.equalTo(backGroundView)
}
okButton.snp.makeConstraints { (make) in
make.width.equalTo(80)
make.height.equalTo(30)
make.centerX.equalTo(backGroundView.snp.centerX)
make.bottom.equalTo(backGroundView).offset(-20)
}
closeButton.snp.makeConstraints { (make) in
make.width.height.equalTo(30)
make.left.equalTo(20)
make.top.equalTo(10)
}
backgroundImageView.image = selectedImage
imageScrollview.delegate = self
imageScrollview.minimumZoomScale = 1.0
imageScrollview.maximumZoomScale = 6.0
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return backgroundImageView
}
#objc func closeClick(sender:UIButton) {
self.removeFromSuperview()
}
#objc func okClick(sender:UIButton) {
if let topCotroller = UIApplication.shared.gettopMostViewController() {
self.removeFromSuperview()
let mediaDetailController = UploadDetailsViewController.instantiate(fromAppStoryboard: .Upload)
mediaDetailController.mediaImage = selectedImage
if metaData != nil {
mediaDetailController.exifDictionary = metaData![kCGImagePropertyExifDictionary as String] as? [String : AnyObject]
}
topCotroller.navigationController?.pushViewController(mediaDetailController, animated: true)
}
}
}
Following is code to add cameraPreview inside current ControllerView
let imagePreview = cameraPreview(image: image, frame: UIScreen.main.bounds,metaData:metaData)
self.view.addSubview(imagePreview)
your other component is zoom in-out because you put those component inside ScrollView.if you simply put those component out side of ScrollView than your component will not zoom in-out with respect to ScrollView.
Following is Source Code.
class cameraPreview : UIView , UIScrollViewDelegate {
var selectedImage : UIImage!
var backGroundView = UIView()
var imageScrollview = UIScrollView()
var metaData : [String:Any]?
let backgroundImageView = UIImageView()
var closeButton : UIButton = {
let button = UIButton(type: UIButtonType.custom)
button.setImage(UIImage(named:"closeWhite"), for: .normal)
button.addTarget(self, action: #selector(closeClick), for: .touchUpInside)
return button
}()
var okButton : UIButton = {
let button = UIButton()
button.setTitle("OK", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 15)
button.setTitleColor(UIColor.black, for: .normal)
button.backgroundColor = UIColor.white
button.layer.cornerRadius = 15
button.addTarget(self, action: #selector(okClick), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
commoninit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commoninit()
}
required init(image:UIImage,frame:CGRect,metaData:[String:Any]?) {
super.init(frame: frame)
self.selectedImage = image
self.metaData = metaData
commoninit()
}
override func layoutSubviews() {
}
func commoninit() {
self.backgroundColor = UIColor.white
backGroundView.backgroundColor = UIColor.clear
backgroundImageView.contentMode = .scaleAspectFit
self.addSubview(imageScrollview)
imageScrollview.addSubview(backGroundView)
backGroundView.addSubview(backgroundImageView)
self.addSubview(closeButton)
self.addSubview(okButton)
imageScrollview.snp.makeConstraints { (make) in
make.edges.equalTo(self)
}
backGroundView.snp.makeConstraints { (make) in
make.edges.equalTo(imageScrollview)
make.height.width.equalTo(self)
}
backgroundImageView.snp.makeConstraints { (make) in
make.edges.equalTo(backGroundView)
}
okButton.snp.makeConstraints { (make) in
make.width.equalTo(80)
make.height.equalTo(30)
make.centerX.equalTo(self)
make.bottom.equalTo(self).offset(-20)
}
closeButton.snp.makeConstraints { (make) in
make.width.height.equalTo(30)
make.left.equalTo(20)
make.top.equalTo(10)
}
backgroundImageView.image = selectedImage
imageScrollview.delegate = self
imageScrollview.minimumZoomScale = 1.0
imageScrollview.maximumZoomScale = 6.0
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return backgroundImageView
}
#objc func closeClick(sender:UIButton) {
self.removeFromSuperview()
}
#objc func okClick(sender:UIButton) {
if let topCotroller = UIApplication.shared.gettopMostViewController() {
self.removeFromSuperview()
let mediaDetailController = UploadDetailsViewController.instantiate(fromAppStoryboard: .Upload)
mediaDetailController.mediaImage = selectedImage
if metaData != nil {
mediaDetailController.exifDictionary = metaData![kCGImagePropertyExifDictionary as String] as? [String : AnyObject]
}
topCotroller.navigationController?.pushViewController(mediaDetailController, animated: true)
}
}
}

Adding a UIControl into title of Navbar Programmatically - Swift

I'm trying to add a custom UI Segmented control I created into my root view controller's navbar. Here's my code:
Segmented Control:
#IBDesignable class FeedViewSC: UIControl {
fileprivate var labels = [UILabel]()
var thumbView = UIView()
var items: [String] = ["Tab1", "Tab2"] {
didSet {
setupLabels()
}
}
var selectedIndex : Int = 0 {
didSet{
displayNewSelectedIndex()
}
}
#IBInspectable var font : UIFont! = UIFont.systemFont(ofSize: 13) {
didSet {
setFont()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView() {
layer.cornerRadius = 2
layer.borderColor = UIColor(red: 2/255, green: 239/255, blue: 23/255, alpha: 1).cgColor
backgroundColor = UIColor(red: 239/255, green: 29/255, blue: 239/255, alpha: 1)
setupLabels()
insertSubview(thumbView, at: 0)
}
func setupLabels() {
for label in labels {
label.removeFromSuperview()
}
labels.removeAll(keepingCapacity: true)
for index in 1...items.count {
let label = UILabel(frame: CGRect.zero)
label.text = items[index-1]
label.textAlignment = .center
label.font = UIFont(name: "timesnr",size: 17)
label.textColor = UIColor(red: 51/255, green: 51/255, blue: 51/255, alpha: 1)
self.addSubview(label)
labels.append(label)
}
}
override func layoutSubviews() {
super.layoutSubviews()
var selectFrame = self.bounds
let newWidth = selectFrame.width / CGFloat(items.count)
selectFrame.size.width = newWidth
thumbView.frame = selectFrame
thumbView.backgroundColor = UIColor(red: 255/255, green: 255/255, blue: 255/255, alpha: 1)
thumbView.layer.cornerRadius = 5
let labelHeight = self.bounds.height
let labelWidth = self.bounds.width / CGFloat(labels.count)
for index in 0...labels.count - 1 {
let label = labels[index]
let xPosition = CGFloat(index) * labelWidth
label.frame = CGRect(x: xPosition, y: 0, width: labelWidth, height: labelHeight)
}
}
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self)
var calculatedIndex: Int?
for (index, item) in labels.enumerated() {
if item.frame.contains(location){
calculatedIndex = index
}
}
if calculatedIndex != nil {
selectedIndex = calculatedIndex!
sendActions(for: .valueChanged)
}
return false
}
func displayNewSelectedIndex (){
if(self.selectedIndex == -1){
self.selectedIndex = self.items.count-1
}
let label = labels[selectedIndex]
}
func setFont(){
for item in labels {
item.font = font
}
}
}
My VC that I would liek to add this Segmented Control to:
class FeedViewController: UIViewController {
let feedViewSC: FeedViewSC = {
let sc = FeedViewSC()
sc.translatesAutoresizingMaskIntoConstraints = false
return sc
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
view.addSubview(feedViewSC)
setupFeedViewSC()
}
func setupFeedViewSC() {
feedViewSC.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor, constant: 5).isActive = true
feedViewSC.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
feedViewSC.heightAnchor.constraint(equalToConstant: 35).isActive = true
feedViewSC.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 60).isActive = true
feedViewSC.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -60).isActive = true
}
override func viewDidAppear(_ animated: Bool) {
let img = UIImage()
self.navigationController?.navigationBar.shadowImage = img
self.navigationController?.navigationBar.setBackgroundImage(img, for: UIBarMetrics.default)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
If you can tell me how I can add my custom UIControl to my View Controller's Navigation bar title.
If the FeedViewController is the initial view controller of the NavigationController you can do it very simply by
let feedControl = FeedViewSC(frame: (self.navigationController?.navigationBar.bounds)!)
feedControl.autoresizingMask = [.flexibleWidth,.flexibleHeight]
self.navigationController?.navigationBar.addSubview(feedControl)
feedControl.addTarget(self, action: #selector(FeedViewController.changingTab), for: .valueChanged)
At least I don't see a reason that this would not work for getting it in the navigation bar.
Also not part of the question but if you are having any trouble seeing your control in IB might I suggest.
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.cornerRadius = 2
layer.borderColor = UIColor(red: 2/255, green: 239/255, blue: 23/255, alpha: 1).cgColor
backgroundColor = UIColor(red: 239/255, green: 29/255, blue: 239/255, alpha: 1)
setupLabels()
insertSubview(thumbView, at: 0)
}
As for the control itself I did not test it but your events and your handling may be slightly different than value changed I am not sure.
You could also make the navigation bar of the controller a special designable class and never add it in code but you would probably have to get a reference in the viewDidLoad to use it. The designable would look like
import UIKit
#IBDesignable class DesignableNavBar: UINavigationBar {
var feedControl : FeedViewSC!
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView() {
if feedControl == nil{
feedControl = NavControl(frame: self.bounds)
feedControl.autoresizingMask = [.flexibleHeight,.flexibleWidth]
self.addSubview(feedControl)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupView()
}
}
And then in your controller in say the viewDidLoad you could do this.
if let navController = self.navigationController{
if navController.navigationBar is DesignableNavBar{
let control = (navController.navigationBar as! DesignableNavBar). feedControl
control?.addTarget(self, action: #selector(ViewController.changingTab), for: .valueChanged)
}
}

How to change borders of UIPageControl dots?

Changing the colours is pretty straightforward, but is it possible to change the border of all unselected dots?
Ex:
dot.layer.borderWidth = 0.5
dot.layer.borderColor = UIColor.blackColor()
Yes This can be done..
Actually its pretty simple.
For iOS 14 Apple has introduced a great customization, where you can set custom images and even set background
let pageControl = UIPageControl()
pageControl.numberOfPages = 5
pageControl.backgroundStyle = .prominent
pageControl.preferredIndicatorImage = UIImage(systemName: "bookmark.fill")
pageControl.setIndicatorImage(UIImage(systemName: "heart.fill"), forPage: 0)
For prior to iOS 14:-
The Pagecontrol is composed of many Subviews which you can access. self.pageControl.subviews returns you [UIView] i.e array of UIView's.
After you get a single view you can add border to it , change its borderColor, change its border width, transform the dot size like scaling it.. All those properties that a UIView has can be used.
for index in 0..<array.count{ // your array.count
let viewDot = weakSelf?.pageControl.subviews[index]
viewDot?.layer.borderWidth = 0.5
viewDot?.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
if (index == indexPath.row){ // indexPath is the current indexPath of your selected cell or view in the collectionView i.e which needs to be highlighted
viewDot?.backgroundColor = UIColor.black
viewDot?.layer.borderColor = UIColor.black.cgColor
}
else{
viewDot?.backgroundColor = UIColor.white
viewDot?.layer.borderColor = UIColor.black.cgColor
}
}
and it looks like this
And remember you do not need to set weakSelf?.pageControl.currentPage = indexPath.row.Do let me know in case of any problem.. Hope this solves your problem.
All the best
iOS 14 allows setting indicator image with SFSymbol here's my subclassing of UIPageControl
class BorderedPageControl: UIPageControl {
var selectionColor: UIColor = .black
override var currentPage: Int {
didSet {
updateBorderColor()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
currentPageIndicatorTintColor = selectionColor
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func updateBorderColor() {
if #available(iOS 14.0, *) {
let smallConfiguration = UIImage.SymbolConfiguration(pointSize: 8.0, weight: .bold)
let circleFill = UIImage(systemName: "circle.fill", withConfiguration: smallConfiguration)
let circle = UIImage(systemName: "circle", withConfiguration: smallConfiguration)
for index in 0..<numberOfPages {
if index == currentPage {
setIndicatorImage(circleFill, forPage: index)
} else {
setIndicatorImage(circle, forPage: index)
}
}
pageIndicatorTintColor = selectionColor
} else {
subviews.enumerated().forEach { index, subview in
if index != currentPage {
subview.layer.borderColor = selectionColor.cgColor
subview.layer.borderWidth = 1
} else {
subview.layer.borderWidth = 0
}
}
}
}
}
Extension for set pagecontrol indicator border / Swift 3
extension UIImage {
class func outlinedEllipse(size: CGSize, color: UIColor, lineWidth: CGFloat = 1.0) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.setStrokeColor(color.cgColor)
context.setLineWidth(lineWidth)
let rect = CGRect(origin: .zero, size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)
context.addEllipse(in: rect)
context.strokePath()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
USE:
let image = UIImage.outlinedEllipse(size: CGSize(width: 7.0, height: 7.0), color: .lightGray)
self.pageControl.pageIndicatorTintColor = UIColor.init(patternImage: image!)
self.pageControl.currentPageIndicatorTintColor = .lightGray
If anybody wants to CustomUIPageControl, then might need this
#IBDesignable
class CustomPageControl: UIView {
var dotsView = [RoundButton]()
var currentIndex = 0
#IBInspectable var circleColor: UIColor = UIColor.orange {
didSet {
updateView()
}
}
#IBInspectable var circleBackgroundColor: UIColor = UIColor.clear {
didSet {
updateView()
}
}
#IBInspectable var numberOfDots: Int = 7 {
didSet {
updateView()
}
}
#IBInspectable var borderWidthSize: CGFloat = 1 {
didSet {
updateView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func updateView() -> Void {
for v in self.subviews{
v.removeFromSuperview()
}
dotsView.removeAll()
let stackView = UIStackView()
stackView.axis = NSLayoutConstraint.Axis.horizontal
stackView.distribution = UIStackView.Distribution.fillEqually
stackView.alignment = UIStackView.Alignment.center
stackView.spacing = 10
stackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(stackView)
//Constraints
stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
NSLayoutConstraint.activate([
stackView.heightAnchor.constraint(equalToConstant: 20.0)
])
stackView.removeFullyAllArrangedSubviews()
for i in 0..<numberOfDots {
let button:RoundButton = RoundButton(frame: CGRect.zero)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.heightAnchor.constraint(equalToConstant: 10.0),
button.widthAnchor.constraint(equalToConstant: 10.0),
])
button.tag = i
button.layer.borderWidth = 1
// button.backgroundColor = circleBackgroundColor
// button.layer.borderWidth = borderWidthSize
// button.layer.borderColor = circleColor.cgColor
button.addTarget(self, action:#selector(self.buttonClicked), for: .touchUpInside)
stackView.addArrangedSubview(button)
dotsView.append(button)
}
}
func updateCurrentDots(borderColor : UIColor, backColor : UIColor, index : Int){
for button in dotsView{
if button == dotsView[index]{
button.backgroundColor = backColor
button.layer.borderColor = borderColor.cgColor
}else{
button.backgroundColor = .clear
button.layer.borderColor = borderColor.cgColor
}
}
}
#objc func buttonClicked() {
print("Button Clicked")
}
class RoundButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
}
override func layoutSubviews() {
self.layer.cornerRadius = self.frame.size.width / 2
}
}
extension UIStackView {
func removeFully(view: UIView) {
removeArrangedSubview(view)
view.removeFromSuperview()
}
func removeFullyAllArrangedSubviews() {
arrangedSubviews.forEach { (view) in
removeFully(view: view)
}
}
}
You can use either Programmatically or Stoaryboard
To update the current dots calls this function
self.pageControl.updateCurrentDots(borderColor: .white, backColor: .white, index:1)

Resources