I'm trying to build a user interface based on generic classes which are based on the same extension of the UIViewController class. This example puts squares on the screen and the tap event would be dealt with in the parent class, but it doesnt work. What am I doing wrong?
Main ViewController.swift:
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//FIRST SQUARE
let widget1 = TestWidget()
widget1.viewX = 10
widget1.viewY = 10
self.view.addSubview(widget1.view)
self.addChildViewController(widget1)
widget1.didMoveToParentViewController(self)
//SECOND SQUARE
let widget2 = TestWidget()
widget2.viewX = 100
widget2.viewY = 100
self.view.addSubview(widget2.view)
self.addChildViewController(widget2)
widget2.didMoveToParentViewController(self)
}
}
Here is the TestWidget class:
class TestWidget : UIViewController {
var viewX : CGFloat!
var viewY : CGFloat!
override func viewDidLoad() {
super.viewDidLoad()
let square = UIView()
square.frame = CGRect(x : viewX, y : viewY, width: 50, height: 50)
square.backgroundColor = UIColor.orangeColor()
self.view.addSubview(square)
//GESTURE PART
var tapForSquare = UITapGestureRecognizer()
tapForSquare.addTarget(self, action : "onTap")
square.addGestureRecognizer(tapForSquare)
square.userInteractionEnabled = true
square.multipleTouchEnabled = true
}
func onTap() {
println("square tapped")
}
}
I have both squares on the screen but the tap action only works on the second one. Is there any problem with this?
onTap() takes an argument. It should be:
onTap(gestureRecognizer: UITapGestureRecognizer)
and add a colon after "onTop" in this line:
tapForSquare.addTarget(self, action : "onTap")
Thanks everyone, it seems interestingly this works only if you set the parent views frame as well.
So something like:
override func viewDidLoad() {
super.viewDidLoad()
self.view.frame = CGRect(x : viewX, y : viewY, width: 50, height: 50)
let square = UIView()
square.frame = CGRect(x : 0, y : 0, width: 50, height: 50)
square.backgroundColor = UIColor.orangeColor()
self.view.addSubview(square)
...
}
Related
hope this is an easy one...
I am in a empty new project.
I have added a custom view called MyCustomView:
import UIKit
public class MyCustomView: UIView{
private var littleView: UIView!
open class func show() -> UIView{
let bigView = MyCustomView()
bigView.configureView()
UIApplication.shared.keyWindow?.addSubview(bigView)
return bigView
}
private func configureView(){
let screenSize = UIScreen.main.bounds.size
self.frame = CGRect(x: 0,
y: 0,
width: screenSize.width,
height: screenSize.height)
littleView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
littleView.backgroundColor = .black
addSubview(littleView)
}
}
In the ViewController doing this:
override func viewDidLoad() {
let test = MyFirstView.show()
}
I hoped this will present the view, but I still have to use self.view.addSubview(test) to see it....
I thought with UIApplication.shared.keyWindow?.addSubview(bigView) and adding a subView to it, it should present the View.
What am I missing?
Add the subview in viewDidAppear
override func viewDidAppear() {
let test = MyFirstView.show()
}
I am trying to add a Page Control on top of my UIScrollView Custom class. Everything is wokring fine. But whenever I slide the scrollView, the Page Control will slide along the first Image as well.
Here is my code:
import Foundation
import UIKit
import SwiftyGif
class MBSPagingScrollView: UIScrollView {
var pageControl = UIPageControl()
var imagesArray = [String]()
override init (frame : CGRect) {
super.init(frame : frame)
}
convenience init () {
self.init(frame:CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
init(frame : CGRect, imagesarray : [String]){
super.init(frame : frame)
imagesArray = imagesarray
self.isPagingEnabled = true
self.showsHorizontalScrollIndicator = false
for (index, imageName) in imagesArray.enumerated() {
var imageView = UIImageView()
if imageName.hasSuffix(".gif") {
let gifManager = SwiftyGifManager(memoryLimit:30)
let gif = UIImage(gifName: imageName)
imageView = UIImageView(gifImage: gif, manager: gifManager)
}
else {
imageView = UIImageView(image: UIImage(named: imageName))
imageView.contentMode = UIViewContentMode.scaleToFill
}
imageView.frame = CGRect(x: CGFloat(index) * self.frame.size.width, y: 0, width: self.frame.size.width, height: self.frame.size.height)
self.addSubview(imageView)
}
self.contentSize = CGSize(width:self.frame.size.width * CGFloat(imagesArray.count),height: self.frame.size.height)
configurePageControl()
pageControl.addTarget(self, action: #selector(self.changePage(sender:)), for: UIControlEvents.valueChanged)
}
func configurePageControl() {
self.pageControl.frame = CGRect(x: 0, y: self.frame.size.height - 50, width: self.frame.size.width, height: 50)
self.pageControl.numberOfPages = imagesArray.count
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.red
self.pageControl.pageIndicatorTintColor = UIColor.black
self.pageControl.currentPageIndicatorTintColor = MainOrangeColor
self.addSubview(pageControl)
}
// // MARK : TO CHANGE WHILE CLICKING ON PAGE CONTROL
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * self.frame.size.width
self.setContentOffset(CGPoint(x:x, y:0), animated: true)
}
func selfDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = round(self.contentOffset.x / self.frame.size.width)
pageControl.currentPage = Int(pageNumber)
}
}
And here are images of the problem happening
As you can see the Page Control is sliding with the first image. I need it to stay in the center.
Any help would be appreciated!
I think you can do that by going to Storyboard, and from the left menu of the UI Builder, drag your page control down and put it out of your scroll view and under it, like in the following picture:
Then you'll need to add the suitable constraints to place your page control properly.
Good luck!
If you don’t want it to scroll... don’t add it to the scroll View.
Just add the page control to the self.view instead of to the scroll view.
I'm new to IOS programming , but strangely same code works in a different class so I don't have any idea why isn't it working
It's in Swift 3
class BottomMenu : NSObject{
//Container view
var bottomView: UIView!
//Button
var buttonContents : UIButton!
init(bottomView : UIView) {
super.init()
self.bottomView = bottomView
buttonContents = UIButton(frame: getFrame(index: 0))
buttonContents.backgroundColor = UIColor.blue //To see where to click on the screen
bottomView.addSubview(buttonContents)
buttonContents.addTarget(self, action: #selector(onClick), for: .touchUpInside)
}
func getFrame(index : CGFloat!) -> CGRect{
let screenW = UIScreen.main.bounds.width
return CGRect(x: (screenW/3) * index,
y: 0,
width: screenW/3,
height: self.bottomView.frame.size.height)
}
func onClick(sender: UIButton!){
NSLog("click")
}
}
here's the implementation of this class :
let bottomMenu = BottomMenu(bottomView: bottomNavBarContainer)
So, the "click" log never shows
Your code works fine in Playground. The error must be in the usage of the class. Parent view has userInteraction disabled or such.
import UIKit
import Foundation
import PlaygroundSupport
class BottomMenu : NSObject{
//Container view
var bottomView: UIView!
//Button
var buttonContents : UIButton!
init(bottomView : UIView) {
super.init()
self.bottomView = bottomView
buttonContents = UIButton(frame: getFrame(index: 0))
buttonContents.backgroundColor = UIColor.blue //To see where to click on the screen
bottomView.addSubview(buttonContents)
buttonContents.addTarget(self, action: #selector(onClick), for: .touchUpInside)
}
func getFrame(index : CGFloat!) -> CGRect{
let screenW = UIScreen.main.bounds.width
return CGRect(x: (screenW/3) * index,
y: 0,
width: screenW/3,
height: self.bottomView.frame.size.height)
}
func onClick(sender: UIButton!){
NSLog("click")
}
}
var container = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
container.addSubview(view)
let buttonContents = BottomMenu(bottomView: view)
PlaygroundPage.current.liveView = container
NSObject is not a UIResponder, so it cannot process UIEvent. A solution could be to make the class descend from UIView instead of NSObject or exposing the button to add the target inside of a ViewController.
The selector #selector(onClick) indicates a function with no parameters. Your onClick function has a single parameter, so the selector should be #selector(onClick(sender:)). At least I think that's the selector format in Swift 3. I'm Still getting used to Swift selector syntax.
For Swift 3 adaptation, there are two main changes when adding a target.
Change your selector to
buttonContents.addTarget(self, action: #selector(BottomMenu.onClick(_:)), for: .touchUpInside)
Add underscore to the method
func onClick(_ sender: UIButton!){
Just to clarify:
class BottomMenu : NSObject{
//Container view
var bottomView: UIView!
//Button
var buttonContents : UIButton!
init(bottomView : UIView) {
super.init()
self.bottomView = bottomView
buttonContents = UIButton(frame: getFrame(index: 0))
buttonContents.backgroundColor = UIColor.blue //To see where to click on the screen
bottomView.addSubview(buttonContents)
buttonContents.addTarget(self, action: #selector(self.onClick(_:)), for: .touchUpInside)
}
func getFrame(index : CGFloat!) -> CGRect{
let screenW = UIScreen.main.bounds.width
return CGRect(x: (screenW/3) * index,
y: 0,
width: screenW/3,
height: self.bottomView.frame.size.height)
}
func onClick(_ sender: UIButton!){
NSLog("click")
}
}
So far I've created a rectangle that starts from the bottom and moves upward using UiDynamicAnimator. I would like the user to determine the "strength" of the negative gravity. I want the user to determine the value through a slider.
This is my code so far:
import UIKit
class ViewController: UIViewController {
var orangeSquare: UIView?
var animator: UIDynamicAnimator?
override func viewDidLoad() {
super.viewDidLoad()
func sliderChanged(sender: AnyObject) {
var sliderValue = sender.value
}
//Create animation
let dim = CGRectMake(100, 500, 200, 100)
orangeSquare = UIView(frame: dim)
orangeSquare?.backgroundColor = UIColor.orangeColor()
//Add item to the screen
self.view.addSubview(orangeSquare!)
//Initialize the animator
animator = UIDynamicAnimator(referenceView: self.view)
//Add gravity
let gravity = UIGravityBehavior(items: [orangeSquare!])
let direction = CGVectorMake(0.0, sliderValue)
gravity.gravityDirection = direction
//Collision
let boundries = UICollisionBehavior(items: [orangeSquare!])
boundries.translatesReferenceBoundsIntoBoundary = true
//Add animations
animator?.addBehavior(boundries)
animator?.addBehavior(gravity)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I get two errors:
"Ambiguous use of ´value´" and
"Use of unresolved identifier ´sliderValue´"
How do I convert ´sliderValue´ into a float with just one decimal point?
your code is missing a few things. sliderValue is an unresolved identifier because you have only declared it within sliderChanged but are referring to it in the main body of viewDidLoad. Also, I think that your use of value is ambiguous because you have declared the parameter to the function as AnyObject, whose value could be any one of a number of things!
Your code was missing a mechanism linking a change in the value of the slider with a change in the gravity behaviour. As such, I've implemented this using an explicit target attached to the slider object. I've also thrown in a label showing the magnitude of the gravitational force. This is quite rough but I think it achieves what you were looking to do.
import UIKit
class ViewController: UIViewController {
var dynamicAnimator : UIDynamicAnimator!
var gravityBehaviour : UIGravityBehavior!
var orangeSquare : UIView!
var slider : UISlider!
var sliderLabel : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Subviews
self.orangeSquare = {
let oS : UIView = UIView(frame: CGRect(origin: CGPoint(x: (self.view.frame.width / 2) - 100, y: 500), size: CGSize(width: 200, height: 200)))
oS.backgroundColor = UIColor.orange
return oS
}()
self.slider = UISlider(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: self.view.frame.width - 400, height: 50)))
self.slider.addTarget(self, action: #selector(self.sliderValueDidChange), for: UIControlEvents.allTouchEvents)
self.slider.minimumValue = -5
self.slider.maximumValue = 5
self.slider.value = 0
self.sliderLabel = UILabel(frame: CGRect(origin: CGPoint(x: self.view.frame.width - 100, y: 100), size: CGSize(width : 50, height: 50)))
self.sliderLabel.backgroundColor = UIColor.red
self.sliderLabel.textAlignment = NSTextAlignment.center
self.sliderLabel.textColor = UIColor.white
self.sliderLabel.text = String(self.slider.value)
// Assemble
self.view.addSubview(self.orangeSquare)
self.view.addSubview(self.slider)
self.view.addSubview(self.sliderLabel)
// Configure dynamic behaviours
self.dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
self.gravityBehaviour = UIGravityBehavior(items: [self.orangeSquare])
self.gravityBehaviour.gravityDirection = CGVector(dx: 0, dy: 0)
// Configure boundaries
let boundaries : UICollisionBehavior = UICollisionBehavior(items: [self.orangeSquare])
boundaries.translatesReferenceBoundsIntoBoundary = true
self.dynamicAnimator.addBehavior(self.gravityBehaviour)
self.dynamicAnimator.addBehavior(boundaries)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func sliderValueDidChange() {
// When the slider value changes, update the label text and the gravity vector
self.sliderLabel.text = String((round(self.slider.value) * 10) / 10)
self.gravityBehaviour.gravityDirection = CGVector(dx: 0, dy: CGFloat(-1 * self.slider.value))
}
}
Hope that helps. All best!
import UIKit
class ViewController: UIViewController {
var dynamicAnimator : UIDynamicAnimator!
var gravityBehaviour : UIGravityBehavior!
var orangeSquare : UIView!
var slider : UISlider!
var sliderLabel : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Subviews
self.orangeSquare = {
let oS : UIView = UIView(frame: CGRect(origin: CGPoint(x: (self.view.frame.width / 2) - 100, y: 500), size: CGSize(width: 200, height: 200)))
oS.backgroundColor = UIColor.orangeColor()
return oS
}()
self.slider = UISlider(frame: CGRect(origin: CGPoint(x: self.view.frame.width - 100, y: 100), size: CGSize(width: self.view.frame.width - 300, height: 150)))
self.slider.addTarget(self, action: #selector(self.sliderValueDidChange), forControlEvents: UIControlEvents.AllTouchEvents)
self.slider.minimumValue = -10
self.slider.maximumValue = 10
self.slider.value = 0
self.sliderLabel = UILabel(frame: CGRect(origin: CGPoint(x: self.view.frame.width - 100, y: 200), size: CGSize(width : 50, height: 50)))
self.sliderLabel.backgroundColor = UIColor.redColor()
self.sliderLabel.textAlignment = NSTextAlignment.Center
self.sliderLabel.textColor = UIColor.whiteColor()
self.sliderLabel.text = String(self.slider.value)
// Assemble
self.view.addSubview(self.orangeSquare)
self.view.addSubview(self.slider)
self.view.addSubview(self.sliderLabel)
// Configure dynamic behaviours
self.dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
self.gravityBehaviour = UIGravityBehavior(items: [self.orangeSquare])
self.gravityBehaviour.gravityDirection = CGVector(dx: 0.0, dy: 0.0)
// Configure boundaries
let boundaries : UICollisionBehavior = UICollisionBehavior(items: [self.orangeSquare])
boundaries.translatesReferenceBoundsIntoBoundary = true
self.dynamicAnimator.addBehavior(self.gravityBehaviour)
self.dynamicAnimator.addBehavior(boundaries)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func sliderValueDidChange() {
// When the slider value changes, update the label text and the gravity vector
self.sliderLabel.text = String((round(self.slider.value) * 10) / 10)
self.gravityBehaviour.gravityDirection = CGVector(dx: 0, dy: CGFloat(-1 * self.slider.value))
}
}
I am trying to stick the UIView on top while scrolling. Tried using max(x:T, y:T) method as suggested here on stackOverflow but it does not work. I manage to detect when the scrollView should be re-positioned but updating frame does not have any affect.
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate{
#IBOutlet var objectView: UIView!
#IBOutlet var scrollView: UIScrollView!
//var originalOffsetY : CGFloat!
//var originalOffestX : CGFloat!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let size = CGSize.init(width: self.view.frame.width, height: self.view.frame.height*2)
scrollView.contentSize = size
scrollView.backgroundColor = UIColor.blueColor()
objectView.backgroundColor = UIColor.whiteColor()
scrollView.delegate = self
scrollView.addSubview(objectView)
}
// override func viewWillAppear(animated: Bool) {
// originalOffsetY = objectView.frame.origin.y
// originalOffestX = objectView.frame.origin.x
// }
func scrollViewDidScroll(scrollView: UIScrollView) {
print("ScrollView \(scrollView.contentOffset.y)")
print("ObjectView \(objectView.frame.origin.y)")
let location = CGPointMake(0, scrollView.contentOffset.y)
let size = objectView.frame.size
if scrollView.contentOffset.y >= objectView.frame.origin.y {
objectView.frame = CGRect.init(origin: location, size: size)
print("Detected \(objectView.frame)")
}
// var newRect = objectView.frame
// newRect.origin.y = max(originalOffsetY!, scrollView.contentOffset.y)
// objectView.frame = newRect
}
}
The condition is matched successfully as can be seen here in Logs . But the view remains unchanged. I require the objectView to scroll a bit.. from around y=270 to 0 .. but not beyond it.
You need to keep track of the original position of the view within the scroll view, and calculate using that value, here's a very simplified example:
class StickyScrollViewController: UIViewController {
var stickyView = UIView(frame: CGRect(x: 0, y: 100, width: 300, height: 100))
var originalStickyOrigin: CGPoint = CGPoint(x: 0, y: 100)
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var nextYOffset = max(originalStickyOrigin.y, scrollView.contentOffset.y)
stickyView.frame = CGRect(x: 0, y: nextYOffset, width: 300, height: 100)
}
}