Swift pinch to zoom - swift

I'm trying to add an image and scrollview programmatically to swift, and then allow them to be pinched to zoom.
I've followed the instructions from UIImageView pinch zoom swift but am still not able to pinch to zoom, I'm not sure what I'm missing
class mapViewController: UIViewController, UIScrollViewDelegate, IALocationManagerDelegate {
var imageViewBackground: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x:0, y:0, width:width-70, height:height-60))
var image: UIImage?
let urlString = "https://firebasestorage.googleapis.com/v0/b/coxur-59760.appspot.com/o/Maps%2FCapture.png?alt=media&token=741b1425-cb01-453b-8c40-47f6e5fd528d"
let url = NSURL(string: urlString)! as URL
if let imageData: NSData = NSData(contentsOf: url) {
image = UIImage(data: imageData as Data)
}
imageViewBackground.image = image
// you can change the content mode:
imageViewBackground.contentMode = UIView.ContentMode.scaleAspectFill
var scrollView: UIScrollView!
scrollView = UIScrollView(frame: CGRect(x: 0, y: 120, width: width, height: height))
scrollView.contentSize = CGSize(width: width, height: 2000)
scrollView.delegate = self
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 6.0
scrollView.addSubview(imageViewBackground)
self.view.addSubview(scrollView)
//self.view.sendSubviewToBack(imageViewBackground)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageViewBackground
}
}
What am I doing wrong here?

The problem is this line:
let imageViewBackground = UIImageView(frame: CGRect(x:0, y:0, width:width-70, height:height-60))
Delete let. Problem solved.

Sorry do not have the reputation to comment but could it possibly be that you are initializing the scrollView inside of the viewDidLoad instead of making it a class variable?

Related

Swift Xib UiView BottomSheet being called multiple times

so i make this bottomsheet view with xib and theres nothing wrong with my code, its just i only want to show it once, i mean like everytime i click the button its get triggered. which is fine but if i rapidly click the button it will also load bunch of time according on how many times i click. i only want to show once i mean no matter how much you rapidly click it only gonna show the xib view once, until i dismiss the button on the xib and it will do the same thing.
here's some video to make it more clearly
https://drive.google.com/file/d/12pwGdTiP_1QZlYc8tV-BlIIQQ5yYrfto/view?usp=sharing
i put a gif on that gdrive link
for the code
Xib Controller :
OrderActionSheetView: UIViewController {
#IBOutlet weak var Text: UILabel!
#IBOutlet weak var vieww: UIView!
#IBOutlet weak var botView: UIView!
#IBAction func cobaLagiBTn(_ sender: Any) {
let closeView = screenSize.height
UIView.animate(withDuration: 0.3, animations: {
self.view.alpha = 0.0
self.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
let frame = self.view.frame
self.view.frame = CGRect(x: 0, y: closeView, width: frame.width, height: frame.height)
})
}
let fullView: CGFloat = 0
let screenSize: CGRect = UIScreen.main.bounds
override func viewDidLoad() {
super.viewDidLoad()
vieww.layer.cornerRadius = 20
vieww.layer.borderWidth = 0.5
vieww.layer.borderColor = UIColor(red:222/255, green:225/255, blue:227/255, alpha: 1).cgColor
vieww.clipsToBounds = true
botView.layer.masksToBounds = false
botView.layer.shadowColor = UIColor.black.cgColor
botView.layer.shadowOpacity = 0.14
botView.layer.shadowOffset = CGSize(width: 0, height: 0)
botView.layer.shadowRadius = 2.7
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.3) { [weak self] in
let frame = self?.view.frame
let yComponent = self?.fullView
self?.view.frame = CGRect(x: 0, y: yComponent!, width: frame!.width, height: frame!.height)
}
}
func prepareBackgroundView(){
let blurEffect = UIBlurEffect.init(style: .light)
let visualEffect = UIVisualEffectView.init(effect: blurEffect)
let bluredView = UIVisualEffectView.init(effect: blurEffect)
bluredView.contentView.addSubview(visualEffect)
visualEffect.frame = UIScreen.main.bounds
bluredView.frame = UIScreen.main.bounds
view.insertSubview(bluredView, at: 0)
}
func Show(){
self.view.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.view.alpha = 0.0
UIView.animate(withDuration: 0.25, animations: {
self.view.alpha = 1.0
self.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
})
}
}
View Controller :
class BottomSheetViewController: UIViewController {
#IBAction func Button(_ sender: Any) {
let text = "Connection Failed"
addBottomSheetView(text: text)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func addBottomSheetView(text : String) {
// 1- Init bottomSheetVC
let bottomSheetVC = OrderActionSheetView()
// 2- Add bottomSheetVC as a child view
self.addChild(bottomSheetVC)
self.view.addSubview(bottomSheetVC.view)
bottomSheetVC.didMove(toParent: self)
// 3- Adjust bottomSheet frame and initial position.
let height = view.frame.height
let width = view.frame.width
bottomSheetVC.view.frame = CGRect(x: 0, y: self.view.frame.maxY, width: width, height: height)
bottomSheetVC.Text.text = text
}
}
i just need to know how to stop popping up twice, cause its kinda really some big bugs.....
like i said earlier i just want the xib view to shown only once no matter how many times you rapidly click the button.
Thanks guys :)
As here you add a new instance every click
func addBottomSheetView(text : String) {
// 1- Init bottomSheetVC
let bottomSheetVC = OrderActionSheetView()
// 2- Add bottomSheetVC as a child view
self.addChild(bottomSheetVC)
self.view.addSubview(bottomSheetVC.view)
bottomSheetVC.didMove(toParent: self)
So either
1-You need to add a bool variable like
var isShown = false
and in beginning of method add this code
guard !isShown else { return }
isShown = true
and when you remove the view make
isShown = false
Or
2- create an instance variable like
var bottomSheetVC:OrderActionSheetView?
and in beginning of method
guard bottomSheetVC == nil else { return }
bottomSheetVC = OrderActionSheetView()
and when you remove it do
bottomSheetVC.view.removeFromSuperview()
bottomSheetVC = nil

How to connect Pagecounter to ScrollView

Trying to connect my pagecounter to my scroll view, but for some reason it does not change when the image is scrolled.
Wrote code calculating the image size change in the scroll view.
#IBOutlet weak var myScrollView: UIScrollView!
#IBOutlet weak var pageCounter: UIPageControl!
var page = 0
var myImages = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var posX: CGFloat = 0
let scrollWidth = myScrollView.frame.size.width
let scrollHeight = myScrollView.frame.size.height
myScrollView.isPagingEnabled = true
myImages = [#imageLiteral(resourceName: "61763233_2273795276270684_2413481963462590464_o"), #imageLiteral(resourceName: "64431295_2284501085200103_3314220009403711488_n"), #imageLiteral(resourceName: "hrc1"), #imageLiteral(resourceName: "66263822_2295361957447349_1103112725925462016_n"), #imageLiteral(resourceName: "62022794_2274568069526738_785458383527346176_o")]
view.setNeedsLayout()
view.layoutIfNeeded()
pageCounter.numberOfPages = myImages.count
pageCounter.pageIndicatorTintColor = UIColor.gray
pageCounter.currentPageIndicatorTintColor = UIColor.blue
for img in myImages {
let imgView = UIImageView(frame: CGRect(x: posX, y: 0, width: scrollWidth, height: scrollHeight))
imgView.image = img
imgView.contentMode = .scaleAspectFill
imgView.clipsToBounds = true
myScrollView.addSubview(imgView)
myScrollView.contentSize = CGSize(width: scrollWidth * CGFloat(myImages.count), height: scrollHeight)
posX = posX + scrollWidth
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = myScrollView.frame.size.width
let getpage = round(myScrollView.contentOffset.x / pageWidth)
let currentPage = Int(getpage)
page = currentPage
pageCounter.currentPage = page
}
The page counter should change after every scroll.
set your PageCounter Inside your ScrollView
example:
where pageCounter is your object and scrollView is your scrollView
NSLayoutConstraint.activate([
pageCounter.topAnchor.constraint(equalTo: self.scrollView.topAnchor, constant: 15),
pageCounter.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor, constant: 30),
pageCounter.heightAnchor.constraint(equalToConstant: 40),
pageCounter.widthAnchor.constraint(equalToConstant: 40)
])
"myScrollview.delegate = self"
this line of code was missing. Making it impossible to connect with the page control

UIScrollView showing subview not correctly

I'm trying to show simple custom view into scrollView. Here's my code :
struct scrollViewDataStruct {
let title: String?
let image: UIImage?
}
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
var scrollViewData = [scrollViewDataStruct]()
override func viewDidLoad() {
super.viewDidLoad()
scrollViewData = [
scrollViewDataStruct(title: "First", image: #imageLiteral(resourceName: "iPhone 8 Copy 2")),
scrollViewDataStruct(title: "Second", image: #imageLiteral(resourceName: "iPhone 8 Copy 3"))
]
self.scrollView.backgroundColor = .yellow
scrollView.contentSize.width = self.scrollView.frame.width * CGFloat(scrollViewData.count)
var i = 0
for _ in scrollViewData {
let view = CustomView(frame: CGRect(x: self.scrollView.frame.width * CGFloat(i), y: 0, width: scrollView.frame.width, height: self.scrollView.frame.height))
self.scrollView.addSubview(view)
i += 1
}
// Do any additional setup after loading the view, typically from a nib.
}
}
class CustomView: UIView {
let imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.backgroundColor = UIColor.blue
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(imageView)
imageView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
As you can see, the CustomView's frame = scrollView's frame but when i ran application it's not as I expected :
Then, in storyboard, i change device from iphone8 to iphone 8 plus and run again. It's show CustomView correctly. I have no idea, the scrollView is always correct but the CustomView is not .
Any suggest ?
Your problem is that you are accessing the frame of the scrollView before Auto Layout has run and established the size of the frame for the actual device. A quick fix is to move your setup code into an override of viewDidLayoutSubviews.
You have to be careful though, because unlike viewDidLoad, viewDidLayoutSubviews will run more than once, so you have to make sure you don't add your views multiple times.
// property - have we set up the views yet?
var setup = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if !setup {
scrollView.contentSize.width = self.scrollView.frame.width * CGFloat(scrollViewData.count)
var i = 0
for _ in scrollViewData {
let view = CustomView(frame: CGRect(x: self.scrollView.frame.width * CGFloat(i), y: 0, width: scrollView.frame.width, height: self.scrollView.frame.height))
self.scrollView.addSubview(view)
i += 1
}
setup = true
}
}
You should consider using constraints to place your views within your scrollView content instead of messing with the frame calculations, then Auto Layout would just automatically do the right thing.
In viewDidLoad, UI component will suppose to have the size you have taken in storyboard.
There are 2 ways to do this:
1. Use autoresizingMask property
autoresizingMask property will resize the view, if its containerView's frames changed
var i = 0
for _ in scrollViewData {
let view = CustomView(frame: CGRect(x: self.scrollView.frame.width * CGFloat(i), y: 0, width: scrollView.frame.width, height: self.scrollView.frame.height))
view.autoresizingMask = .flexibleHeight
self.scrollView.addSubview(view)
i += 1
}
2. Use fixed parameters, say UIScreen.main.bounds.size.height
Just update your code for custom view's height with reference to screen height rather than scroll view's height. It will work fine
var i = 0
let height = UIScreen.main.bounds.size.height
for _ in scrollViewData {
let view = CustomView(frame: CGRect(x: self.scrollView.frame.width * CGFloat(i), y: 0, width: scrollView.frame.width, height: height))
self.scrollView.addSubview(view)
i += 1
}

iOS - add image and text in title of Navigation bar

I would like to create a nav bar similar to what's in the image that's attached.
The title of the nav bar will be a combination of an image and text.
Should this be done per any best practice?
How can it be done?
As this answer shows, the easiest solution is to add the text to your image and add that image to the navigation bar like so:
var image = UIImage(named: "logo.png")
self.navigationItem.titleView = UIImageView(image: image)
But if you have to add text and an image separately (for example, in the case of localization), you can set your navigation bar's title view to contain both image and text by adding them to a UIView and setting the navigationItem's title view to that UIView, for example (assuming the navigation bar is part of a navigation controller):
// Only execute the code if there's a navigation controller
if self.navigationController == nil {
return
}
// Create a navView to add to the navigation bar
let navView = UIView()
// Create the label
let label = UILabel()
label.text = "Text"
label.sizeToFit()
label.center = navView.center
label.textAlignment = NSTextAlignment.Center
// Create the image view
let image = UIImageView()
image.image = UIImage(named: "Image.png")
// To maintain the image's aspect ratio:
let imageAspect = image.image!.size.width/image.image!.size.height
// Setting the image frame so that it's immediately before the text:
image.frame = CGRect(x: label.frame.origin.x-label.frame.size.height*imageAspect, y: label.frame.origin.y, width: label.frame.size.height*imageAspect, height: label.frame.size.height)
image.contentMode = UIViewContentMode.ScaleAspectFit
// Add both the label and image view to the navView
navView.addSubview(label)
navView.addSubview(image)
// Set the navigation bar's navigation item's titleView to the navView
self.navigationItem.titleView = navView
// Set the navView's frame to fit within the titleView
navView.sizeToFit()
Use horizontal UIStackView should be much cleaner and easier
Please add the next extension to UIViewController
extension UIViewController {
func setTitle(_ title: String, andImage image: UIImage) {
let titleLbl = UILabel()
titleLbl.text = title
titleLbl.textColor = UIColor.white
titleLbl.font = UIFont.systemFont(ofSize: 20.0, weight: .bold)
let imageView = UIImageView(image: image)
let titleView = UIStackView(arrangedSubviews: [imageView, titleLbl])
titleView.axis = .horizontal
titleView.spacing = 10.0
navigationItem.titleView = titleView
}
}
then use it inside your viewController:
setTitle("yourTitle", andImage: UIImage(named: "yourImage"))
(this will align the text and the icon together to the center, if you want the text to be centered and the icon in the left, just add an empty UIView with width constraint equal to the icon width)
here is my 2 cents for Swift 4, since accepted answer didn't work for me (was mostly off the screen):
// .. in ViewController
var navBar = CustomTitleView()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// =================== navBar =====================
navBar.loadWith(title: "Budget Overview", leftImage: Images.pie_chart)
self.navigationItem.titleView = navBar
}
class CustomTitleView: UIView
{
var title_label = CustomLabel()
var left_imageView = UIImageView()
override init(frame: CGRect){
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
setup()
}
func setup(){
self.addSubview(title_label)
self.addSubview(left_imageView)
}
func loadWith(title: String, leftImage: UIImage?)
{
//self.backgroundColor = .yellow
// =================== title_label ==================
//title_label.backgroundColor = .blue
title_label.text = title
title_label.font = UIFont.systemFont(ofSize: FontManager.fontSize + 5)
// =================== imageView ===================
left_imageView.image = leftImage
setupFrames()
}
func setupFrames()
{
let height: CGFloat = Navigation.topViewController()?.navigationController?.navigationBar.frame.height ?? 44
let image_size: CGFloat = height * 0.8
left_imageView.frame = CGRect(x: 0,
y: (height - image_size) / 2,
width: (left_imageView.image == nil) ? 0 : image_size,
height: image_size)
let titleWidth: CGFloat = title_label.intrinsicContentSize.width + 10
title_label.frame = CGRect(x: left_imageView.frame.maxX + 5,
y: 0,
width: titleWidth,
height: height)
contentWidth = Int(left_imageView.frame.width)
self.frame = CGRect(x: 0, y: 0, width: CGFloat(contentWidth), height: height)
}
var contentWidth: Int = 0 //if its CGFloat, it infinitely calls layoutSubviews(), changing franction of a width
override func layoutSubviews() {
super.layoutSubviews()
self.frame.size.width = CGFloat(contentWidth)
}
}
Swift 4.2 + Interface Builder Solution
As a follow-on to Lyndsey Scott's answer, you can also create a UIView .xib in Interface Builder, use that to lay out your title and image, and then update it on-the-fly via an #IBOutlet. This is useful for dynamic content, internationalization, maintainability etc.
Create a UIView subclass with a UILabel outlet and assign your new .xib to this class:
import UIKit
class FolderTitleView: UIView {
#IBOutlet weak var title : UILabel!
/// Create an instance of the class from its .xib
class func instanceFromNib() -> FolderTitleView {
return UINib(nibName: "FolderTitleView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! FolderTitleView
}
}
Connect the label to your outlet (title in my example) in your .xib, then in your UIViewController:
/// Reference to the title view
var folderTitleView : FolderTitleView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Set the screen title to match the active folder
updateTitle()
}
/// Updates the title of the navigation controller.
func updateTitle() {
self.title = ""
if folderTitleView == nil {
folderTitleView = FolderTitleView.instanceFromNib()
self.navigationItem.titleView = folderTitleView
}
folderTitleView!.title.text = "Listening"
folderTitleView!.layoutIfNeeded()
}
This results in a nice self-centering title bar with an embedded image that you can easily update from code.
// worked for me
create a view and set the frame
now add the image in the view and set the frame
after adding the image, add the label in same view and set the frame
after adding the image and label to view, add same view to navigationItem
let navigationView = UIView(frame: CGRect(x: 0, y: 0, width: 50 , height: 55))
let labell : UILabel = UILabel(frame: CGRect(x: -38, y: 25, width: 150, height: 25))
labell.text = "Your text"
labell.textColor = UIColor.black
labell.font = UIFont.boldSystemFont(ofSize: 10)
navigationView.addSubview(labell)
let image : UIImage = UIImage(named: ValidationMessage.headerLogoName)!
let imageView = UIImageView(frame: CGRect(x: -20, y: 0, width: 100, height: 30))
imageView.contentMode = .scaleAspectFit
imageView.image = image
//navigationItem.titleView = imageView
navigationView.addSubview(imageView)
navigationItem.titleView = navigationView

Rounded corners for UITabBar

I would like to create a UITabBar with rounded corners. Is there a way I can make the UITabBar have rounded corners? It would take the shape of the second picture.
The app starts off with a tableView. When the user taps a topic, they are sent to a tabBar controller.
-----edit-----
This is my AppDelegate:
func application(_application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool{
let tabBarController = window?.rootViewController as! UITabBarController
let image = UIImage(named: "bar")
let tabBarImage = resize(image: image!, newWidth: tabBarController.view.frame.width)
tabBarController.tabBar.backgroundImage = tabBarImage
return true
}
func resize(image: UIImage, newWidth: CGFloat) -> UIImage {
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: image.size.height))
image.drawInRect( CGRect(x: 0, y: 0, width: newWidth, height: image.size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
Edit: Dec '21
Replaced image to omit name on project.
Hope this help you
self.tabBar.layer.masksToBounds = true
self.tabBar.isTranslucent = true
self.tabBar.barStyle = .blackOpaque
self.tabBar.layer.cornerRadius = 20
self.tabBar.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
Its working fine for me
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.tabBarController?.tabBar.layer.masksToBounds = true
self.tabBarController?.tabBar.isTranslucent = true
self.tabBarController?.tabBar.barStyle = .default
self.tabBarController?.tabBar.layer.cornerRadius = 25
self.tabBarController?.tabBar.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
}
Hey I used a simple property of the tabBar which is backgroundImage.
So, I added in the appDelegate in didFinisLaunchingWithOptions:
let tabBarController = window?.rootViewController as! UITabBarController
let image = UIImage(named: "bar")
let tabBarImage = resize(image: image!, newWidth: tabBarController.view.frame.width)
tabBarController.tabBar.backgroundImage = tabBarImage
and my custom method to change the size of the image:
func resize(image: UIImage, newWidth: CGFloat) -> UIImage {
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: image.size.height))
image.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: image.size.height)) // image.drawInRect( CGRect(x: 0, y: 0, width: newWidth, height: image.size.height)) in swift 2
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
I used a png image as the background, the same that you posted but with clear color instead of the black color.
Hope this helps
self.tabBarController?.tabBar.layer.cornerRadius = 20
self.tabBarController?.tabBar.layer.masksToBounds = true
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: view.frame.width, height: tabBar.frame.height), cornerRadius: 15)
let mask = CAShapeLayer()
mask.path = path.cgPath
tabBar.layer.mask = mask
}
In my case works this way:
import UIKit
#IBDesignable
class CustomTabBar: UITabBar {
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
private func setup() {
layer.masksToBounds = true
isTranslucent = true
barStyle = .blackOpaque
layer.cornerRadius = 44
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
}
}
Then set class CustomTabBar for TabBar in storyboard: