Swift: How can I bind data to a view in a loop? - ios

I want to bind data to an array of UIImageView-es by for-loop. But I don't know how to bind as simple as Javascript data attribute.
I have an idea that making an array [UIImage : data] to do this.
Ok, here is something wrong with my code:
Questions:
How to get its patternSelector in selectDecoration?
How to create outlets binding with data in a loop? For example, How can I bind every variable pattern to each action selectDecoration() ?
func selectDecoration(recognizer: UITapGestureRecognizer) {
print("OK")
}
let patternSelectorHeight: CGFloat = 120
var i : Int = 0
//var patternViews = [UIImageView]()
for pattern in decorationPatterns {
let patternSelector = UIImageView(image: UIImage(named: "test.jpg"))
if let p = patternSelector.image {
let proportion = p.size.width / p.size.height
let w = proportion * patternSelectorHeight
let patternX: CGFloat = (w + Conf.Size.margin) * CGFloat(i) + Conf.Size.margin
let patternOrigin = CGPoint(x: patternX, y: decorationSelectionTitleHeight)
patternSelector.userInteractionEnabled = true
patternSelector.frame = CGRect(origin: patternOrigin, size: CGSize(width: w, height: patternSelectorHeight))
patternSelector.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "selectDecoration:"))
decorationBg.addSubview(patternSelector)
decorationBg.bringSubviewToFront(patternSelector)
//patternViews.append(patternSelector)
}
i++
}

There is no inbuilt support for data binding in Swift. You need to keep a reference to your UIImageView objects, probably, as you suggest, in an array, so that you can update them later.
You need to set patterSelector.userInteractionEnabled=true for the UITapGestureRecognizer to work.
let patterSelectorSize = CGSize(width: 80.0, height: 160.0)
var imageViews = [UIImageView]() // This probably needs to be an instance variable so you can access it outside of this function
var i : Int = 0
for _ in decorationPatterns {
let patternX: CGFloat = (patterSelectorSize.width + 10) * CGFloat(i) + 10
let patternOrigin = CGPoint(x: patternX, y: CGFloat(60))
let patterSelector = UIImageView(image: UIImage(named: "default.jpeg"))
patterSelector.frame = CGRect(origin: patternOrigin, size: patterSelectorSize)
patterSelector.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "selectDecoration:"))
patterSelector.userInteractionEnabled=true
imageViews.append(patterSelector)
decorationBg.addSubview(patterSelector)
decorationBg.bringSubviewToFront(patterSelector)
i++
}
You must also ensure that userInteractionEnabled is true in decorationBg otherwise touches won't be propagated to your image views

(1) How to create outlets binding with data in a loop?
If you would like to bind String data then try accessibilityLabel property. You can set & retrieve value silently.
(2) why the UITapGestureRecoginzer in follow doesn't work at all?
You missed to enable the userInteractionEnabled of your UIImageView
Hope this helps you.

Related

Adding a marking to CGRect that is in Path block in Swift

I am trying to make a rectangle that is made of little squares in it. I am able to create the rectangle using the code given but I want to add a marking on the squares. This way the user can make a distinction between each and every square. I want it to look like a grid.
func drawBoard(boundingRect: CGSize) -> some View {
let columns = self.numColumns
let rows = self.numRows
let blocksize = min(boundingRect.width/CGFloat(columns), boundingRect.height/CGFloat(rows))
let xoffset = (boundingRect.width - blocksize*CGFloat(columns))/2
let yoffset = (boundingRect.height - blocksize*CGFloat(rows))/2
let gameBoard = self.gameBoard
return ForEach(0...columns-1, id:\.self) { (column:Int) in
ForEach(0...rows-1, id:\.self) { (row:Int) in
Path { path in
let x = xoffset + blocksize * CGFloat(column)
let y = boundingRect.height - yoffset - blocksize*CGFloat(row+1)
let rect = CGRect(x: x, y: y, width: blocksize, height: blocksize)
path.addRect(rect)
}
.fill(gameBoard[column][row].color)
}
}
}
I am willing to change the entire code. I just need to make a grid...
GRID
Something like this or with borders or anything. Please help with this solution.

Trouble indexing into array within asynchronous call, Swift

I'm trying to create a scrollView of "Match" object images that correlates to an array of "Match" objects within my view controller, so that if I tap onto an image of a "Match" in the scrollView, I can take the index of that image in my miniMatchesContainer, and use it to access the Match object within my array that that image corresponds to. I tried going about it with a for-loop, but the problem is that since I'm getting match images from the server asynchronously, the calls return out of order and so my containerView indexes are off (I've added an image of my console's print statemen to show what I mean). So now I'm at a bit of an impasse, and would appreciate some advice as to where to go from here. Should I change my approach? Is there something I'm missing? Code added below.
//function to create contentScrollView for MiniMyatches
func setupMiniContentScroll(contentScroll: UIScrollView) {
let scalar:Double = 4/19
let contentViewDimension = contentScroll.frame.width * CGFloat(scalar)
let contentScrollWidth = CGFloat(LocalUser.matches.count) * (contentViewDimension + CGFloat(12)) - CGFloat(12)
let matchManager = MatchesManager()
for index in 0..<LocalUser.matches.count {
let match = LocalUser.matches[index]
print("Match index: \(index), Match at Index: \(match.itemName)")
matchManager.retrieveMatchThumbnail(match) { img, error in
if let img = img {
//create the mini matches views
let xOrigin = index == 0 ? 12 : CGFloat(index) * contentViewDimension + (CGFloat(12) * CGFloat(index) + CGFloat(12))
let contentFrame = CGRectMake(xOrigin, 10, contentViewDimension, contentViewDimension)
let contentView = self.makeMiniContentView(contentFrame, image: img, matchedPrice: match.matchedPrice)
let tap = UITapGestureRecognizer(target: self, action: #selector(BrowseViewController.toggleItemInfo(_:)))
contentView.addGestureRecognizer(tap)
self.miniMatchContainer.append(contentView)
print("MiniMatchContainer Index: \(self.miniMatchContainer.indexOf(contentView)), Match at Index: \(match.itemName)")
//update the contentScrollView
dispatch_async(dispatch_get_main_queue()) {
let contentLabelFrame = CGRect(x: xOrigin, y: contentFrame.height + 15, width: contentFrame.width, height: 20)
let contentLabel = self.makeMiniContentLabel(contentLabelFrame, itemName: match.itemName)
let priceLabel = self.makeMiniPriceLabel(contentFrame, matchedPrice: match.matchedPrice)
contentScroll.addSubview(contentView)
contentScroll.addSubview(contentLabel)
contentScroll.addSubview(priceLabel)
contentScroll.contentSize = CGSizeMake(contentScrollWidth + CGFloat(16), contentScroll.frame.height)
}
}
}
}
}
I would try:
print("MiniMatchContainer Index: \(index), Match at Index: \(match.itemName)")
Instead of:
print("MiniMatchContainer Index: \(self.miniMatchContainer.indexOf(contentView)), Match at Index: \(match.itemName)")
And I would create the content views outside retrieveMatchThumbnail, updating it inside.

UIImage wont recognize tap gesture Swift

I'm trying to add a tap gesture recognizer to some UIImages that are in a side scrollview, but the images flat out won't recognize the tap and I can't see where I went wrong. I've tried "scrollView.bringSubViewToFront(imgView)" because I figured they might be getting buried in layers of other views, but that didn't do the trick either. "contentView" is the UIImageView in question, where my scrollView is just a collection of those. Any help here would be appreciated, thank you.
//function to create contentScrollView for MiniMatches
func setupMiniContentScroll(contentScroll: UIScrollView) {
let scalar:Double = 4/19
let contentViewDimension = contentScroll.frame.width * CGFloat(scalar)
let contentScrollWidth = CGFloat(LocalUser.matches.count) * (contentViewDimension + CGFloat(12)) - CGFloat(12)
let matchManager = MatchesManager()
for index in 0..<LocalUser.matches.count {
let match = LocalUser.matches[index]
matchManager.retrieveMatchThumbnail(match) { img, error in
if let img = img {
//create the mini matches views
let xOrigin = index == 0 ? 12 : CGFloat(index) * contentViewDimension + (CGFloat(12) * CGFloat(index) + CGFloat(12))
let contentFrame = CGRectMake(xOrigin, 10, contentViewDimension, contentViewDimension)
let contentView = self.makeMiniContentView(contentFrame, image: img, matchedPrice: match.matchedPrice)
let tap = UITapGestureRecognizer(target: self, action: #selector(BrowseViewController.toggleItemInfo(_:)))
contentView.addGestureRecognizer(tap)
self.miniMatchContainer.append(contentView)
//update the contentScrollView
dispatch_async(dispatch_get_main_queue()) {
let contentLabelFrame = CGRect(x: xOrigin, y: contentFrame.height + 15, width: contentFrame.width, height: 20)
let contentLabel = self.makeMiniContentLabel(contentLabelFrame, itemName: match.itemName)
let priceLabel = self.makeMiniPriceLabel(contentFrame, matchedPrice: match.matchedPrice)
contentScroll.addSubview(contentView)
contentScroll.addSubview(contentLabel)
contentScroll.addSubview(priceLabel)
contentScroll.contentSize = CGSizeMake(contentScrollWidth + CGFloat(16), contentScroll.frame.height)
}
}
}
}
}
Did you set UIImage property userInteractionEnabled to true?

How to pass arguments from one class into a UIView class? Swift

I have a UIView class in my app which plots a line graph. In there, I assign my graphPoints variables like so :
var graphPoints:[Int] = [1,2,3,5,7,9]
var graphPoints2:[Int] = [1,2,3,5,7,9]
What I want to do is pass an array of Int from another class and assign those variables, but I am not sure how to do it. Initially i put all my code into one func with array [Int] as parameters and called it from another class but it stopped plotting the graph altogether. How do i do this?
Here is my UIVIew GraphPlotter class code :
import UIKit
#IBDesignable class GraphPlotter: UIView {
var graphPoints:[Int] = [1,2,3,5,7,9]
var graphPoints2:[Int] = [1,2,3,5,7,9]
//1 - the properties for the gradient
var startColor: UIColor = UIColor.redColor()
var endColor: UIColor = UIColor.greenColor()
override func drawRect(rect: CGRect) {
let width = rect.width
let height = rect.height
//set up background clipping area
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: UIRectCorner.AllCorners,
cornerRadii: CGSize(width: 8.0, height: 8.0))
path.addClip()
//2 - get the current context
let context = UIGraphicsGetCurrentContext()
let colors = [startColor.CGColor, endColor.CGColor]
//3 - set up the color space
let colorSpace = CGColorSpaceCreateDeviceRGB()
//4 - set up the color stops
let colorLocations:[CGFloat] = [0.0, 1.0]
//5 - create the gradient
let gradient = CGGradientCreateWithColors(colorSpace,
colors,
colorLocations)
//6 - draw the gradient
var startPoint = CGPoint.zero
var endPoint = CGPoint(x:0, y:self.bounds.height)
CGContextDrawLinearGradient(context,
gradient,
startPoint,
endPoint,
[])
//calculate the x point
let margin:CGFloat = 40.0
let columnXPoint = { (column:Int) -> CGFloat in
//Calculate gap between points
let spacer = (width - margin*2 - 4) /
CGFloat((self.graphPoints.count - 1))
var x:CGFloat = CGFloat(column) * spacer
x += margin + 2
return x
}
// calculate the y point
let topBorder:CGFloat = 60
let bottomBorder:CGFloat = 50
let graphHeight = height - topBorder - bottomBorder
let maxValue = graphPoints2.maxElement()!
let columnYPoint = { (graphPoint2:Int) -> CGFloat in
var y:CGFloat = CGFloat(graphPoint2) /
CGFloat(maxValue) * graphHeight
y = graphHeight + topBorder - y // Flip the graph
return y
}
// draw the line graph
UIColor.flatTealColor().setFill()
UIColor.flatTealColor().setStroke()
//set up the points line
let graphPath = UIBezierPath()
//go to start of line
graphPath.moveToPoint(CGPoint(x:columnXPoint(0),
y:columnYPoint(graphPoints2[0])))
//add points for each item in the graphPoints array
//at the correct (x, y) for the point
for i in 1..<graphPoints.count {
let nextPoint = CGPoint(x:columnXPoint(i),
y:columnYPoint(graphPoints2[i]))
graphPath.addLineToPoint(nextPoint)
}
//Create the clipping path for the graph gradient
//1 - save the state of the context (commented out for now)
CGContextSaveGState(context)
//2 - make a copy of the path
let clippingPath = graphPath.copy() as! UIBezierPath
//3 - add lines to the copied path to complete the clip area
clippingPath.addLineToPoint(CGPoint(
x: columnXPoint(graphPoints.count - 1),
y:height))
clippingPath.addLineToPoint(CGPoint(
x:columnXPoint(0),
y:height))
clippingPath.closePath()
//4 - add the clipping path to the context
clippingPath.addClip()
let highestYPoint = columnYPoint(maxValue)
startPoint = CGPoint(x:margin, y: highestYPoint)
endPoint = CGPoint(x:margin, y:self.bounds.height)
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, [])
CGContextRestoreGState(context)
//draw the line on top of the clipped gradient
graphPath.lineWidth = 2.0
graphPath.stroke()
//Draw the circles on top of graph stroke
for i in 0..<graphPoints.count {
var point = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints2[i]))
point.x -= 5.0/2
point.y -= 5.0/2
let circle = UIBezierPath(ovalInRect:
CGRect(origin: point,
size: CGSize(width: 5.0, height: 5.0)))
circle.fill()
let label = UILabel(frame: CGRectMake(0, 0, 200, 21))
label.center = CGPointMake(160, 284)
label.textAlignment = NSTextAlignment.Center
// label.text = "I'am a test label"
self.addSubview(label)
}
//Draw horizontal graph lines on the top of everything
let linePath = UIBezierPath()
//top line
linePath.moveToPoint(CGPoint(x:margin, y: topBorder))
linePath.addLineToPoint(CGPoint(x: width - margin,
y:topBorder))
//center line
linePath.moveToPoint(CGPoint(x:margin,
y: graphHeight/2 + topBorder))
linePath.addLineToPoint(CGPoint(x:width - margin,
y:graphHeight/2 + topBorder))
//bottom line
linePath.moveToPoint(CGPoint(x:margin,
y:height - bottomBorder))
linePath.addLineToPoint(CGPoint(x:width - margin,
y:height - bottomBorder))
let color = UIColor.flatTealColor()
color.setStroke()
linePath.lineWidth = 1.0
linePath.stroke()
}
}
DBController, func dosmth where I pass the array :
func dosmth(metadata: DBMetadata!) {
let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let localFilePath = (documentsDirectoryPath as NSString).stringByAppendingPathComponent(metadata.filename)
var newarray = [Int]()
do{
let data = try String(contentsOfFile: localFilePath as String,
encoding: NSASCIIStringEncoding)
print(data)
newarray = data.characters.split(){$0 == ","}.map{
Int(String.init($0).stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()))!}
print(newarray)
}
catch let error { print(error) }
//Probably wrong
GraphPlotter().graphPoints = newarray
GraphPlotter().graphPoints2 = newarray
}
So your drawRect method is based on the two variables graphPoints and graphPoints2. Create a method whose job is to update the arrays of these two variables, and then invoke setNeedsDisplay - which will go on to redraw the view.
func plotGraphPoints(gpArray1 : [Int], andMorePoints gpArray2: [Int] ) {
print("Old Values", self.graphPoints)
self.graphPoints = gpArray1
self.graphPoints2 = gpArray2
print("New values", self.graphPoints)
self.setNeedsDisplay()
}
First, I'd set these up so that any update will redraw the view:
var graphPoints:[Int]? { didSet { setNeedsDisplay() } }
var graphPoints2:[Int]? { didSet { setNeedsDisplay() } }
Note, I made those optionals, because you generally want it to handle the absence of data with nil values rather than dummy values. This does assume, though, that you'll tweak your implementation to detect and handle these nil values, e.g., before you start drawing the lines, do a
guard graphPoints != nil && graphPoints2 != nil else { return }
But, I notice that this whole class is IBDesignable, in which case, you probably want a prepareForInterfaceBuilder that provides sample data:
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
graphPoints = [1,2,3,5,7,9]
graphPoints2 = [1,2,3,5,7,9]
}
Second, your other class needs to have a reference to this custom view.
If this "other" class is the view controller and you added the custom view via IB, you would just add a #IBOutlet for the custom view to this view controller. If you added this custom view programmatically, you'd just keep a reference to it in some property after adding it to the view hierarchy. But, however you added a reference to that view, say graphView, you'd just set these properties:
graphView.graphPoints = ...
graphView.graphPoints2 = ...
If this "other" class is something other than a view controller (and in discussion, it sounds like the class in question is a controller for processing of asynchronous DropBox API), you also need to give that class some mechanism to reference the view controller (and thus the custom view). You can accomplish this by either implementing a "completion handler pattern" or a "delegate-protocol" pattern.

Alternative way to show a UIPageControl (Swift)

I'm trying to add an arrow or like an image to correlate to how many view I have in my UIScrollView in swift. Something in line of this
(UPDATE) - My question is how do you insert an image into your scrollview that will tell it to go forward or backwards depending on how many images you have.
Now I know there is the UIPageControl but I'm looking into an alternative way to show that there is a picture and to the right or right and left. This is the code I have so far. I'll try the best I can to explain what I'm doing.
My picSource.count is data from a website that is carrying pictures
I'm multiplying my number of views by the screenwidth
This is an if else statement that shows if a picture is there post bgImage if not post no photoprovided image.
Let me know if you need more information or if I should revise. Thanks!
// Gallery
if (picSource.count > 1){
var picNum:CGFloat = CGFloat(picSource.count)
// UI News Image Scroller
var scroll = UIScrollView(frame:CGRectMake(-5, 650, screenWidth, 260))
scroll.pagingEnabled = true
scroll.showsHorizontalScrollIndicator = false
scroll.showsVerticalScrollIndicator = false
scroll.contentSize = CGSizeMake(picNum*screenWidth, 220)
// Number of views
var numberOfViews = picSource.count
var imageName = ""
// Loop to add views to scroll view
for (var i = 0; i < numberOfViews; i++){
var myIntValue:Int = Int(screenWidth)
var xOrigin = i * myIntValue
// Creates UIImageView instance using myImage
var scrollImageView = UIImageView(frame: CGRect(origin: CGPoint(x: xOrigin, y: 5), size: CGSize(width: screenWidth, height: 250)))
// Tests which image name needs to be used
imageName = picSource[i]
// Creates image object using specified image name "...jpg"
var url = NSURL(string: "http://www/images/cl/" + adID[adCurrentTag] + "/gallery/" + imageName)
var maybeError: NSError?
if var imageData :NSData = NSData(contentsOfURL: url!,options: NSDataReadingOptions.DataReadingMappedIfSafe, error: &maybeError) {
var bgImage = UIImage(data:imageData)
scrollImageView.contentMode = .ScaleAspectFit
scrollImageView.image = bgImage
// Adds Image to scroll view
scroll.addSubview(scrollImageView)
}else if let error = maybeError {
var photoNotProvided = UIImage(named:"noPhoto800x800.png")
var photoNotProvidedView = UIImageView(frame: CGRectMake(5, 650.0, screenWidth, 300.0))
scrollImageView.image = photoNotProvided
scroll.addSubview(scrollImageView)
}
}
border.addSubview(scroll)

Resources