I am creating a scrollview with a page control. I have a scrollview added to my viewcontroller and inside the scrollview there is a UIImageView. But for some reason I cannot scroll it to the left or right side. Can anyone help me out to fix this problem?
My Code:
var tutorialImages: [String] = ["1", "2", "3"]
var frame = CGRect.zero
func pageControll() {
pageControllTutorial.numberOfPages = tutorialImages.count
for i in 0..<tutorialImages.count {
imageViewTutorial.frame = frame
imageViewTutorial.image = UIImage(named: tutorialImages[i])
scrollViewTutorial.addSubview(imageViewTutorial)
}
scrollViewTutorial.contentSize = CGSize(width: imageViewTutorial.frame.size.width * CGFloat(tutorialImages.count), height: imageViewTutorial.frame.size.height)
scrollViewTutorial.delegate = self
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
pageControllTutorial.currentPage = Int(pageNumber)
}
With the little bit of code you've shown, you're doing a number of things wrong.
Based on this line:
imageViewTutorial.image = UIImage(named: tutorialImages[I])
it looks like you have one UIImageView and you are setting its .image property 3 times, instead of creating 3 different image views.
Also, there is nothing in your code indicating how you are setting the frames of the image views.
I highly recommend using auto-layout instead of explicit frames - makes things much, much easier going forward.
Here is a complete example. It will create a square (1:1 ratio) scroll view 20-pts from the top with 20-pts padding on each side, and a UIPageControl below. It then adds a horizontal UIStackView to the scroll view. That stack view will hold the image views. Once the image views are added, the stack view will automatically define the "scrollable area" -- no need for calculating .contentSize.
Here's what it will look like:
Everything is done in code, so just assign the class of an empty view controller to SlidesExampleViewController ... no #IBOutlet or #IBAction connections needed.
class SlidesExampleViewController: UIViewController, UIScrollViewDelegate {
lazy var pageControl: UIPageControl = {
let v = UIPageControl()
v.backgroundColor = .brown
return v
}()
lazy var scrollView: UIScrollView = {
let v = UIScrollView(frame: .zero)
v.backgroundColor = .yellow
v.delegate = self
return v
}()
lazy var stackView: UIStackView = {
let v = UIStackView()
v.axis = .horizontal
v.alignment = .fill
v.distribution = .fill
v.spacing = 0
return v
}()
let tutorialImages: [String] = ["1", "2", "3"]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
scrollView.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
pageControl.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
view.addSubview(pageControl)
scrollView.addSubview(stackView)
let v = view.safeAreaLayoutGuide
let g = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// constrain scroll view to top, leading, trailing with 20-pts "padding"
scrollView.topAnchor.constraint(equalTo: v.topAnchor, constant: 20.0),
scrollView.leadingAnchor.constraint(equalTo: v.leadingAnchor, constant: 20.0),
scrollView.trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: -20.0),
// constrain scroll view height equal to scroll view width
scrollView.heightAnchor.constraint(equalTo: scrollView.widthAnchor),
// constrain stack view to all 4 sides of scroll view's contentLayoutGuide
stackView.topAnchor.constraint(equalTo: g.topAnchor),
stackView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// constrain stack view height equal to scroll view height
stackView.heightAnchor.constraint(equalTo: scrollView.heightAnchor),
// constrain page control width to 80% of scroll view width
pageControl.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 0.8),
// constrain page control top to 8-pts below scroll view bottom
pageControl.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 8.0),
// constrain page control centerX to centerX of scroll view
pageControl.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor, constant: 0.0),
])
tutorialImages.forEach { name in
if let img = UIImage(named: name) {
let imgView = UIImageView(image: img)
stackView.addArrangedSubview(imgView)
imgView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
}
}
pageControl.numberOfPages = tutorialImages.count
scrollView.isPagingEnabled = true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x/view.frame.width)
pageControl.currentPage = Int(pageIndex)
}
}
Increase the contentSize of scroll view As (number of images * width of image) expecting all image are equal in size.
int startPosition = 0;
set the image origin.x
1 image1.frame.origin.x = startPosition;
startPosition = startPosition + image1.frame.size.width;
2 image2.frame.origin.x = startPosition;
startPosition = startPosition + image2.frame.size.width;
3 image3.frame.origin.x = startPosition;
startPosition = startPosition + image3.frame.size.width;
or either you can put these in a for loop to set the frame size of image.
The frame of each UIImageView is being set to CGRect.zero so they all have a 0 height and width, which means your scroll views contentSize is also 0x0. As far as the scroll view is concerned, there's nothing to scroll here.
Do this instead:
let image = UIImage(named: tutorialImages[i])
imageViewTutorial.frame = CGRect(x: widthSoFar, y: 0, width: image.width, height: image.height)
imageViewTutorial.image = image
scrollViewTutorial.addSubview(imageViewTutorial)
widthSoFar += image.size.width
Initialize widthSoFar outside the for loop to 0.
Always Keep the
Scroll view size is greater or equal to Imageview size.
check and keep UIImageView userInteraction Enable.
keep scroll view Content size as big as your number of image like for four
image in scoll view then (imageview.frame.size.width*4)
Content size = CGSize(imageview.frame.size.width*4,imageview.frame.size.height).
Related
I'm working on trying to recreate the first SwiftUI Tutorial in code without using SwiftUI. In the example, it mentions using a Spacer "to direct the layout to use the full width of the device":
VStack {
Text("Turtle Rock")
HStack {
Text("Joshua Tree National Park")
Spacer()
Text("California")
}
}
For the VStack, HStack, and Text views, I can easily use UIStackView and UILabel. But for the Spacer, I can't seem to find any equivalent in the standard library (no UISpacer or anything like that), which makes me think this is something custom to SwiftUI. In the tutorial, it describes how this Spacer works:
A spacer expands to make its containing view use all of the space of its parent view, instead of having its size defined only by its contents.
So how can I recreate the functionality of this Spacer view programmatically? Do I just add a constraint to the UIStackView to make it full width? Or is there a way to add a subview to the UIStackView that makes it behave like it does in SwiftUI?
I have worked solution using dummy UIView() which has set big constant widthConstraint using low priority - this ensures UIView will grow as much as possible but will not grow over superview constraints since it has lower priority.
Example:
let titleLabel = UILabel()
titleLabel.text = "Joshua Tree National Park"
let spacer = UIView()
// maximum width constraint
let spacerWidthConstraint = spacer.widthAnchor.constraint(equalToConstant: .greatestFiniteMagnitude) // or some very high constant
spacerWidthConstraint.priority = .defaultLow // ensures it will not "overgrow"
spacerWidthConstraint.isActive = true
let descriptionLabel = UILabel()
descriptionLabel.text = "California"
UIStackView(arrangedSubviews: [titleLabel, spacer, descriptionLabel])
Swift 5
You can create an extension for UIView to create a spacer in this way:
extension UIView {
static func spacer(size: CGFloat = 10, for layout: NSLayoutConstraint.Axis = .horizontal) -> UIView {
let spacer = UIView()
if layout == .horizontal {
spacer.widthAnchor.constraint(equalToConstant: size).isActive = true
} else {
spacer.heightAnchor.constraint(equalToConstant: size).isActive = true
}
return spacer
}
}
After that, you can add your spacer into stacks in this way:
// ... some code
let stack = UIStackView(arrangedSubviews: [UIView.spacer(), UILabel(), UIView.spacer(), UILabel(), UIView.spacer()])
stack.axis = .horizontal
stack.distribution = .equalSpacing
// For vertical stacks
let stack = UIStackView(arrangedSubviews: [UIView.spacer(for: .vertical), UILabel(), UIView.spacer(for: .vertical), UILabel(), UIView.spacer(for: .vertical)])
stack.axis = .vertical
stack.distribution = .equalSpacing
I think in this way, you can play with spacer size/layout and stack distribution/alignment to get the desired space.
A plain UIView() can replace a SwiftUI.Spacer as long as distribution is set to fill, since it has no set width constraint. I've only tested this vertically. However, you should note the layout performed by UIStackView and SwiftUI.HStack are not actually the same steps, though the end results may appear similar; something to look into.
I'm trying to create a field of grass and some cows on it.
Using a list of ones and zeros to decide which boxes are supposed to have a cow on it. And then I use flowpane to create my grid. When I try to add the image of a cow to a stackpane it doesn't show.
here is the relevant code:
override val root = flowpane {
this.setPrefSize(viewWidth * tileSize, viewHeight * tileSize)
//imageview("/cow.jpg")
for (i in hage) {
stackpane {
rectangle {
fill = GREEN
width = tileSize
height = tileSize
arcWidth = 5.0
arcHeight = 5.0
if (i == 1) {
/*circle {
fill = RED
radius = 10.0
}*/
imageview("/cow.jpg")
}
}
}
}
}
The out-commented image before the loop/stackpane shows (if it's not out-commented) and the stackpane works how I want it to if cows are represented as circles.
So how come the image isn't showing?
You're adding your ImageView to the Rectangle, not the StackPane. The Rectangle is a shape that doesn't render/support children. Try moving it outside of the rectangle {} block :)
I am newbie on swift 3. I have this situation: I need to create textview dynamically also the remove button below it...so there is button to add field (textview) dynamically then there's button below it to remove it..and I put those on view.. Let say "placeholderCD"
enter image description here
But the problem is..how or what action i need to put when the remove button click? because we can not assigned the id of dynamically button right? so my code like below :
func AddCommentItem(sender:UIButton){
/** CREATE TEXT VIEW **/
let textView: UITextView = UITextView(frame: CGRect(x: 0, y: (newX + 20), width: 311.00, height: 50.00));
textView.textAlignment = NSTextAlignment.justified
//textView.backgroundColor = UIColor.lightGray
let borderColor = UIColor.lightGray
textView.layer.borderColor = borderColor.cgColor
textView.layer.borderWidth = 1.0
textView.tag = tagname
/** CREATE TEXT VIEW **/
/** CREATE REMOVE TEXT VIEW **/
let btnRemove: UIButton = UIButton(frame: CGRect(x: 240, y: (newX + 52), width: 100, height: 50))
//btnRemove.backgroundColor = UIColor.green
btnRemove.setTitle("Remove", for: .normal)
btnRemove.titleLabel?.font = btnRemove.titleLabel?.font.withSize(10)
btnRemove.tag = tagname
btnRemove.setTitleColor(UIColor.gray, for: .normal)
btnRemove.addTarget(self, action: #selector(btnRemoveComment), for: .touchUpInside)
//btnRemove.tag = 1
/** CREATE REMOVE TEXT VIEW **/
cell.placeholderCD.addSubview(textView)
cell.placeholderCD.addSubview(btnRemove)
}
func btnRemoveComment(Sender:UIButton) {
/** HOW I REMOVE THE Related text view object with this action **/
}
you can remove the textview by tag
cell.placeholderCD.viewWithTag(yourTag)?.removeFromSuperview()
my basic code is pretty much this on that tutorial:
https://code.tutsplus.com/tutorials/an-introduction-to-scenekit-fundamentals--cms-23847
I want to make a sunrise behaviour. Something like the lightNode begins on the same height as the constrained cubeNode and move it up so that the shadow shall become smaller over time.
Therefore I tried to move the nodes via SCNAction.move(to...).
Action on lightNode -> nothing happens
Action on the constrained cubeNode -> shadow starts to flicker, but no changing
I tried around with the shadowModes. I think I misunderstood this. No useful results came up.
Has anyone an idea if scenekit supports something like dynamicly changing shadows?
Yes. You can make a beautiful moving shadow. If your spotlight isn't moving it may be because the tutorial had you put a constraint on it. Try removing it.
// lightNode.constraints = [constraint]
I found a way to make it. My major mistakes were:
Somehow it does not work to access the lightNode (it just has no effect, when actions for a lightNode are called)
I tried to go via SCNAction.move(to...). The answer is to use a rotation instead of a longitudinal movement.
The answer was accessing the constraint Node (instead of the lightNode). In this code the cubeNode is replaced with an invisible centerPoint (as the constraint node where the lightNode has to look at). A boxNodehas been added for making a canvas for the shadow. The lightNodehas to be added to the centerPoint, NOT to the scene.
There is the modified viewDidLoad-method. If you want to check this out, open Xcode, start a new project as SceneKit Game and replace the viewDidLoad with the following code.
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let centerPoint = SCNNode.init()
centerPoint.position = SCNVector3Make(0, 0, 0)
scene.rootNode.addChildNode(centerPoint)
let light = SCNLight()
light.type = SCNLight.LightType.spot
light.spotInnerAngle = 30
light.spotOuterAngle = 80
light.castsShadow = true
light.color = UIColor.init(colorLiteralRed: 0.95, green: 0.8, blue: 0.8, alpha: 1)
light.zFar = 200
let lightNode = SCNNode()
lightNode.light = light
lightNode.position = SCNVector3Make(20, 100, 50)
let constraint = SCNLookAtConstraint(target: centerPoint)
constraint.isGimbalLockEnabled = true
lightNode.constraints = [constraint]
centerPoint.addChildNode(lightNode)
let ambientLight = SCNLight.init()
ambientLight.type = SCNLight.LightType.ambient
ambientLight.color = UIColor.darkGray
scene.rootNode.light = ambientLight
// retrieve the ship node
let material = SCNMaterial.init()
material.diffuse.contents = UIColor.yellow
material.lightingModel = SCNMaterial.LightingModel.phong
material.locksAmbientWithDiffuse = true
let boxNode = SCNNode.init(geometry: SCNBox.init(width: 10, height: 0.1, length: 10, chamferRadius: 0))
boxNode.geometry?.firstMaterial = material
boxNode.position = SCNVector3Make(0, -2, 0)
scene.rootNode.addChildNode(boxNode)
// animate spot light rotation
centerPoint.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: CGFloat(M_PI), y: 0, z: CGFloat(M_PI), duration: 5)))
// animate color light change
let lightColorChange: CABasicAnimation = CABasicAnimation.init(keyPath: "color")
lightColorChange.fromValue = UIColor.init(colorLiteralRed: 0.95, green: 0.8, blue: 0.8, alpha: 1)
lightColorChange.toValue = UIColor.init(colorLiteralRed: 0, green: 0, blue: 0.4, alpha: 1)
lightColorChange.duration = 5.0
lightColorChange.autoreverses = true
lightColorChange.repeatCount = Float.infinity
light.addAnimation(lightColorChange, forKey: "changeLight")
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
There is also a color change of the light is considered. With a CABasicAnimation it is possible to change the light.color-property over time. It is not like a perfect sunrise with all color steps, but there is also a way to chain those animations to make it more complex. (For this search for "wenderlich how to create a complex loading animation".
But I did not find a way to change the shadow color. It could be a nice effect to have white shadows on nighttime and black shadows on daytime. light.shadowColor did not help yet. If someone has an idea it is very appreciated.
I have a PdfView inside a CollectionView. Since both have its own scrollviews I have conflicts in scrolling. So i want to disable scrolling for PdfView. How can I do it?
My solution is a little rough, but does work.
I only wanted to stop horizontal scrolling on my PDFViews - my collection view scrolls horizontally.
I made a view that selectively filters out the scroll mouse events and put it over the PFD view.
class HorizontalScrollBlockerView: NSView
{
var scrollNextResponder: NSResponder?
override func scrollWheel(with event: NSEvent) {
guard scrollNextResponder != nil else {
return
}
if fabs(event.deltaX) >= fabs(event.deltaY) {
scrollNextResponder!.scrollWheel(with: event)
} else {
super.scrollWheel(with: event)
}
}
}
I set the view's 'scrollNextResponder' to be the superView of the PDFView.
I also wrote a method that gets the first child scroll view (enclosedScrollView) which makes sure the PDFView is solid in the horizontal axis when correctly scaled.
if let scrollView = pdfView.enclosedScrollView {
scrollView.usesPredominantAxisScrolling = true
scrollView.hasHorizontalScroller = false
scrollView.horizontalScrollElasticity = NSScrollView.Elasticity.none
}
For a regular Scroll View you can remove scrolls by setting horizontal & vertical scrolls to false value. So for a PDF view try this :
NSScrollView *enclosingScrollView = [myPdfView enclosingScrollView];
[enclosingScrollView setHasHorizontalScroller:NO];
[enclosingScrollView setHasVerticalScroller:NO];