我正在实现一个 iPhone 应用程序,我试图在其中构建结合刷卡(与 Tinder 相同)和滚动效果的功能。请查看以下详细信息。
当用户打开应用程序时,它会显示其他用户的个人资料。所以登录用户可以向左(不)或向右(喜欢)刷卡,该卡将从列表中删除,下一张用户卡将出现。现在如果用户不想刷卡,那么他可以向上或向下滚动来查看其他用户的个人资料。
那么,是否可以实现刷卡和滚动功能的组合。
最佳答案
我知道这个问题现在有点老了,但是是的,这正是我过去几个月一直在构建的问题。
我主要是将 UIPanGestureRecognizer 添加到具有自定义流程布局(用于卡片分页等)的 UICollectionView。
这是它的样子:
主要组件逻辑:
import Foundation
/**
The VerticalCardSwiper is a subclass of `UIView` that has a `VerticalCardSwiperView` embedded.
To use this, you need to implement the `VerticalCardSwiperDatasource`.
If you want to handle actions like cards being swiped away, implement the `VerticalCardSwiperDelegate`.
*/
public class VerticalCardSwiper: UIView {
/// The collectionView where all the magic happens.
public var verticalCardSwiperView: VerticalCardSwiperView!
/// Indicates if side swiping on cards is enabled. Default value is `true`.
@IBInspectable public var isSideSwipingEnabled: Bool = true
/// The inset (spacing) at the top for the cards. Default is 40.
@IBInspectable public var topInset: CGFloat = 40 {
didSet {
setCardSwiperInsets()
}
}
/// The inset (spacing) at each side of the cards. Default is 20.
@IBInspectable public var sideInset: CGFloat = 20 {
didSet {
setCardSwiperInsets()
}
}
/// Sets how much of the next card should be visible. Default is 50.
@IBInspectable public var visibleNextCardHeight: CGFloat = 50 {
didSet {
setCardSwiperInsets()
}
}
/// Vertical spacing between CardCells. Default is 40.
@IBInspectable public var cardSpacing: CGFloat = 40 {
willSet {
flowLayout.minimumLineSpacing = newValue
}
}
/// The transform animation that is shown on the top card when scrolling through the cards. Default is 0.05.
@IBInspectable public var firstItemTransform: CGFloat = 0.05 {
willSet {
flowLayout.firstItemTransform = newValue
}
}
public weak var delegate: VerticalCardSwiperDelegate?
public weak var datasource: VerticalCardSwiperDatasource? {
didSet{
numberOfCards = datasource?.numberOfCards(verticalCardSwiperView: self.verticalCardSwiperView) ?? 0
}
}
/// The amount of cards in the collectionView.
fileprivate var numberOfCards: Int = 0
/// We use this horizontalPangestureRecognizer for the vertical panning.
fileprivate var horizontalPangestureRecognizer: UIPanGestureRecognizer!
/// Stores a `CGRect` with the area that is swipeable to the user.
fileprivate var swipeAbleArea: CGRect!
/// The `CardCell` that the user can (and is) moving.
fileprivate var swipedCard: CardCell! {
didSet {
setupCardSwipeDelegate()
}
}
/// The flowlayout used in the collectionView.
fileprivate lazy var flowLayout: VerticalCardSwiperFlowLayout = {
let flowLayout = VerticalCardSwiperFlowLayout()
flowLayout.firstItemTransform = firstItemTransform
flowLayout.minimumLineSpacing = cardSpacing
flowLayout.isPagingEnabled = true
return flowLayout
}()
public override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
fileprivate func commonInit() {
setupVerticalCardSwiperView()
setupConstraints()
setCardSwiperInsets()
setupGestureRecognizer()
}
}
extension VerticalCardSwiper: CardDelegate {
internal func willSwipeAway(cell: CardCell, swipeDirection: SwipeDirection) {
verticalCardSwiperView.isUserInteractionEnabled = false
if let index = verticalCardSwiperView.indexPath(for: cell)?.row {
self.delegate?.willSwipeCardAway?(card: cell, index: index, swipeDirection: swipeDirection)
}
}
internal func didSwipeAway(cell: CardCell, swipeDirection direction: SwipeDirection) {
if let indexPathToRemove = verticalCardSwiperView.indexPath(for: cell){
self.numberOfCards -= 1
swipedCard = nil
self.verticalCardSwiperView.performBatchUpdates({
self.verticalCardSwiperView.deleteItems(at: [indexPathToRemove])
}) { [weak self] (finished) in
if finished {
self?.verticalCardSwiperView.collectionViewLayout.invalidateLayout()
self?.verticalCardSwiperView.isUserInteractionEnabled = true
self?.delegate?.didSwipeCardAway?(card: cell, index: indexPathToRemove.row ,swipeDirection: direction)
}
}
}
}
internal func didDragCard(cell: CardCell, swipeDirection: SwipeDirection) {
if let index = verticalCardSwiperView.indexPath(for: cell)?.row {
self.delegate?.didDragCard?(card: cell, index: index, swipeDirection: swipeDirection)
}
}
fileprivate func setupCardSwipeDelegate() {
swipedCard?.delegate = self
}
}
extension VerticalCardSwiper: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if let panGestureRec = gestureRecognizer as? UIPanGestureRecognizer {
// When a horizontal pan is detected, we make sure to disable the collectionView.panGestureRecognizer so that it doesn't interfere with the sideswipe.
if panGestureRec == horizontalPangestureRecognizer, panGestureRec.direction!.isX {
return false
}
}
return true
}
/// We set up the `horizontalPangestureRecognizer` and attach it to the `collectionView`.
fileprivate func setupGestureRecognizer(){
horizontalPangestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
horizontalPangestureRecognizer.maximumNumberOfTouches = 1
horizontalPangestureRecognizer.delegate = self
verticalCardSwiperView.addGestureRecognizer(horizontalPangestureRecognizer)
verticalCardSwiperView.panGestureRecognizer.maximumNumberOfTouches = 1
}
/**
This function is called when a pan is detected inside the `collectionView`.
We also take care of detecting if the pan gesture is inside the `swipeAbleArea` and we animate the cell if necessary.
- parameter sender: The `UIPanGestureRecognizer` that detects the pan gesture. In this case `horizontalPangestureRecognizer`.
*/
@objc fileprivate func handlePan(sender: UIPanGestureRecognizer){
guard isSideSwipingEnabled else {
return
}
/// The taplocation relative to the superview.
let location = sender.location(in: self)
/// The taplocation relative to the collectionView.
let locationInCollectionView = sender.location(in: verticalCardSwiperView)
/// The translation of the finger performing the PanGesture.
let translation = sender.translation(in: self)
if swipeAbleArea.contains(location) && !verticalCardSwiperView.isScrolling {
if let swipedCardIndex = verticalCardSwiperView.indexPathForItem(at: locationInCollectionView) {
/// The card that is swipeable inside the SwipeAbleArea.
swipedCard = verticalCardSwiperView.cellForItem(at: swipedCardIndex) as? CardCell
}
}
if swipedCard != nil && !verticalCardSwiperView.isScrolling {
/// The angle we pass for the swipe animation.
let maximumRotation: CGFloat = 1.0
let rotationStrength = min(translation.x/swipedCard.frame.width, maximumRotation)
let angle = (CGFloat.pi/10.0) * rotationStrength
switch (sender.state) {
case .began:
break
case .changed:
swipedCard.animateCard(angle: angle, horizontalTranslation: translation.x)
break
case .ended:
swipedCard.endedPanAnimation(angle: angle)
swipedCard = nil
break
default:
swipedCard.resetToCenterPosition()
swipedCard = nil
}
}
}
}
extension VerticalCardSwiper: UICollectionViewDelegate, UICollectionViewDataSource {
/**
Reloads all of the data for the VerticalCardSwiperView.
Call this method sparingly when you need to reload all of the items in the VerticalCardSwiper. This causes the VerticalCardSwiperView to discard any currently visible items (including placeholders) and recreate items based on the current state of the data source object. For efficiency, the VerticalCardSwiperView only displays those cells and supplementary views that are visible. If the data shrinks as a result of the reload, the VerticalCardSwiperView adjusts its scrolling offsets accordingly.
*/
public func reloadData(){
verticalCardSwiperView.reloadData()
}
/**
Register a class for use in creating new CardCells.
Prior to calling the dequeueReusableCell(withReuseIdentifier:for:) method of the collection view,
you must use this method or the register(_:forCellWithReuseIdentifier:) method
to tell the collection view how to create a new cell of the given type.
If a cell of the specified type is not currently in a reuse queue,
the VerticalCardSwiper uses the provided information to create a new cell object automatically.
If you previously registered a class or nib file with the same reuse identifier,
the class you specify in the cellClass parameter replaces the old entry.
You may specify nil for cellClass if you want to unregister the class from the specified reuse identifier.
- parameter cellClass: The class of a cell that you want to use in the VerticalCardSwiper
identifier
- parameter identifier: The reuse identifier to associate with the specified class. This parameter must not be nil and must not be an empty string.
*/
public func register(_ cellClass: AnyClass?, forCellWithReuseIdentifier identifier: String) {
verticalCardSwiperView.register(cellClass, forCellWithReuseIdentifier: identifier)
}
/**
Register a nib file for use in creating new collection view cells.
Prior to calling the dequeueReusableCell(withReuseIdentifier:for:) method of the collection view,
you must use this method or the register(_:forCellWithReuseIdentifier:) method
to tell the collection view how to create a new cell of the given type.
If a cell of the specified type is not currently in a reuse queue,
the collection view uses the provided information to create a new cell object automatically.
If you previously registered a class or nib file with the same reuse identifier,
the object you specify in the nib parameter replaces the old entry.
You may specify nil for nib if you want to unregister the nib file from the specified reuse identifier.
- parameter nib: The nib object containing the cell object. The nib file must contain only one top-level object and that object must be of the type UICollectionViewCell.
identifier
- parameter identifier: The reuse identifier to associate with the specified nib file. This parameter must not be nil and must not be an empty string.
*/
public func register(nib: UINib?, forCellWithReuseIdentifier identifier: String) {
verticalCardSwiperView.register(nib, forCellWithReuseIdentifier: identifier)
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.numberOfCards
}
public func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return (datasource?.cardForItemAt(verticalCardSwiperView: verticalCardSwiperView, cardForItemAt: indexPath.row))!
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
delegate?.didScroll?(verticalCardSwiperView: verticalCardSwiperView)
}
fileprivate func setupVerticalCardSwiperView(){
verticalCardSwiperView = VerticalCardSwiperView(frame: self.frame, collectionViewLayout: flowLayout)
verticalCardSwiperView.decelerationRate = UIScrollViewDecelerationRateFast
verticalCardSwiperView.backgroundColor = UIColor.clear
verticalCardSwiperView.showsVerticalScrollIndicator = false
verticalCardSwiperView.delegate = self
verticalCardSwiperView.dataSource = self
self.numberOfCards = datasource?.numberOfCards(verticalCardSwiperView: verticalCardSwiperView) ?? 0
self.addSubview(verticalCardSwiperView)
}
fileprivate func setupConstraints(){
verticalCardSwiperView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
verticalCardSwiperView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
verticalCardSwiperView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
verticalCardSwiperView.topAnchor.constraint(equalTo: self.topAnchor),
verticalCardSwiperView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
fileprivate func setCardSwiperInsets(){
verticalCardSwiperView.contentInset = UIEdgeInsets(top: topInset, left: sideInset, bottom: topInset + flowLayout.minimumLineSpacing + visibleNextCardHeight, right: sideInset)
}
}
extension VerticalCardSwiper: UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let itemSize = calculateItemSize(for: indexPath.row)
// set cellHeight in the custom flowlayout, we use this for paging calculations.
flowLayout.cellHeight = itemSize.height
if swipeAbleArea == nil {
// Calculate and set the swipeAbleArea. We use this to determine wheter the cell can be swiped to the sides or not.
let swipeAbleAreaOriginY = collectionView.frame.origin.y + collectionView.contentInset.top
swipeAbleArea = CGRect(x: 0, y: swipeAbleAreaOriginY, width: self.frame.width, height: itemSize.height)
}
return itemSize
}
fileprivate func calculateItemSize(for index: Int) -> CGSize {
let cellWidth: CGFloat!
let cellHeight: CGFloat!
let xInsets = sideInset * 2
let yInsets = cardSpacing + visibleNextCardHeight + topInset
// get size from delegate if the sizeForItem function is called.
if let customSize = delegate?.sizeForItem?(verticalCardSwiperView: verticalCardSwiperView, index: index) {
// set custom sizes and make sure sizes are not negative, if they are, don't subtract the insets.
cellWidth = customSize.width - (customSize.width - xInsets > 0 ? xInsets : 0)
cellHeight = customSize.height - (customSize.height - yInsets > 0 ? yInsets : 0)
} else {
cellWidth = verticalCardSwiperView.frame.size.width - xInsets
cellHeight = verticalCardSwiperView.frame.size.height - yInsets
}
return CGSize(width: cellWidth, height: cellHeight)
}
}
自定义流布局:
import UIKit
/// Custom `UICollectionViewFlowLayout` that provides the flowlayout information like paging and `CardCell` movements.
internal class VerticalCardSwiperFlowLayout: UICollectionViewFlowLayout {
/// This property sets the amount of scaling for the first item.
internal var firstItemTransform: CGFloat?
/// This property enables paging per card. The default value is true.
internal var isPagingEnabled: Bool = true
/// Stores the height of a CardCell.
internal var cellHeight: CGFloat!
internal override func prepare() {
super.prepare()
assert(collectionView!.numberOfSections == 1, "Number of sections should always be 1.")
assert(collectionView!.isPagingEnabled == false, "Paging on the collectionview itself should never be enabled. To enable cell paging, use the isPagingEnabled property of the VerticalCardSwiperFlowLayout instead.")
}
internal override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let items = NSArray (array: super.layoutAttributesForElements(in: rect)!, copyItems: true)
items.enumerateObjects(using: { (object, index, stop) -> Void in
let attributes = object as! UICollectionViewLayoutAttributes
self.updateCellAttributes(attributes)
})
return items as? [UICollectionViewLayoutAttributes]
}
// We invalidate the layout when a "bounds change" happens, for example when we scale the top cell. This forces a layout update on the flowlayout.
internal override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
// Cell paging
internal override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// If the property `isPagingEnabled` is set to false, we don't enable paging and thus return the current contentoffset.
guard isPagingEnabled else {
let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
return latestOffset
}
// Page height used for estimating and calculating paging.
let pageHeight = cellHeight + self.minimumLineSpacing
// Make an estimation of the current page position.
let approximatePage = self.collectionView!.contentOffset.y/pageHeight
// Determine the current page based on velocity.
let currentPage = (velocity.y < 0.0) ? floor(approximatePage) : ceil(approximatePage)
// Create custom flickVelocity.
let flickVelocity = velocity.y * 0.3
// Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)
// Calculate newVerticalOffset.
let newVerticalOffset = ((currentPage + flickedPages) * pageHeight) - self.collectionView!.contentInset.top
return CGPoint(x: proposedContentOffset.x, y: newVerticalOffset)
}
internal override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// make sure the zIndex of the next card is higher than the one we're swiping away.
let nextIndexPath = IndexPath(row: itemIndexPath.row + 1, section: itemIndexPath.section)
let nextAttr = self.layoutAttributesForItem(at: nextIndexPath)
nextAttr?.zIndex = nextIndexPath.row
// attributes for swiping card away
let attr = self.layoutAttributesForItem(at: itemIndexPath)
return attr
}
/**
Updates the attributes.
Here manipulate the zIndex of the cards here, calculate the positions and do the animations.
- parameter attributes: The attributes we're updating.
*/
fileprivate func updateCellAttributes(_ attributes: UICollectionViewLayoutAttributes) {
let minY = collectionView!.bounds.minY + collectionView!.contentInset.top
let maxY = attributes.frame.origin.y
let finalY = max(minY, maxY)
var origin = attributes.frame.origin
let deltaY = (finalY - origin.y) / attributes.frame.height
if let itemTransform = firstItemTransform {
let scale = 1 - deltaY * itemTransform
attributes.transform = CGAffineTransform(scaleX: scale, y: scale)
// TODO: add card stack effect (like Shazam)
}
origin.y = finalY
attributes.frame = CGRect(origin: origin, size: attributes.frame.size)
attributes.zIndex = attributes.indexPath.row
}
}
CardCell 代码(UICollectionViewCell 的子类):
import UIKit
/**
The CardCell that the user can swipe away. Based on `UICollectionViewCell`.
The cells will be recycled by the `VerticalCardSwiper`,
so don't forget to override `prepareForReuse` when needed.
*/
@objc open class CardCell: UICollectionViewCell {
internal weak var delegate: CardDelegate?
open override func layoutSubviews() {
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
super.layoutSubviews()
}
open override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
self.layer.zPosition = CGFloat(layoutAttributes.zIndex)
}
open override func prepareForReuse() {
super.prepareForReuse()
// need to unhide a cell for reuse (cell is hidden when swiped away)
self.isHidden = false
}
/**
This function animates the card. The animation consists of a rotation and translation.
- parameter angle: The angle the card rotates while animating.
- parameter horizontalTranslation: The horizontal translation the card animates in.
*/
public func animateCard(angle: CGFloat, horizontalTranslation: CGFloat){
delegate?.didDragCard(cell: self, swipeDirection: determineCardSwipeDirection())
var transform = CATransform3DIdentity
transform = CATransform3DRotate(transform, angle, 0, 0, 1)
transform = CATransform3DTranslate(transform, horizontalTranslation, 0, 1)
self.layer.transform = transform
}
/**
Resets the CardCell back to the center of the VerticalCardSwiperView.
*/
public func resetToCenterPosition(){
let cardCenterX = self.frame.midX
let centerX = self.bounds.midX
let initialSpringVelocity = fabs(cardCenterX - centerX)/100
UIView.animate(withDuration: 0.5,
delay: 0,
usingSpringWithDamping: 0.6,
initialSpringVelocity: initialSpringVelocity,
options: .allowUserInteraction,
animations: { [weak self] in
self?.layer.transform = CATransform3DIdentity
})
}
/**
Called when the pan gesture is ended.
Handles what happens when the user stops swiping a card.
If a certain treshold of the screen is swiped, the `animateOffScreen` function is called,
if the threshold is not reached, the card will be reset to the center by calling `resetToCenterPosition`.
- parameter angle: The angle of the animation, depends on the direction of the swipe.
*/
internal func endedPanAnimation(angle: CGFloat){
let swipePercentageMargin = self.bounds.width * 0.4
let cardCenterX = self.frame.midX
let centerX = self.bounds.midX
// check for left or right swipe and if swipePercentageMargin is reached or not
if (cardCenterX < centerX - swipePercentageMargin || cardCenterX > centerX + swipePercentageMargin){
animateOffScreen(angle: angle)
} else {
self.resetToCenterPosition()
}
}
/**
Animates to card off the screen and calls the `willSwipeAway` and `didSwipeAway` functions from the `CardDelegate`.
- parameter angle: The angle that the card will rotate in (depends on direction). Positive means the card is swiped to the right, a negative angle means the card is swiped to the left.
*/
fileprivate func animateOffScreen(angle: CGFloat){
var transform = CATransform3DIdentity
let direction = determineCardSwipeDirection()
transform = CATransform3DRotate(transform, angle, 0, 0, 1)
switch direction {
case .Left:
transform = CATransform3DTranslate(transform, -(self.frame.width * 2), 0, 1)
break
case .Right:
transform = CATransform3DTranslate(transform, (self.frame.width * 2), 0, 1)
break
default: break
}
self.delegate?.willSwipeAway(cell: self, swipeDirection: direction)
UIView.animate(withDuration: 0.2, animations: { [weak self] in
self?.layer.transform = transform
}){ (completed) in
self.isHidden = true
self.delegate?.didSwipeAway(cell: self, swipeDirection: direction)
}
}
fileprivate func determineCardSwipeDirection() -> SwipeDirection {
let cardCenterX = self.frame.midX
let centerX = self.bounds.midX
if cardCenterX < centerX {
return .Left
} else if cardCenterX > centerX {
return .Right
} else {
return .None
}
}
}
你可以在github上找到它: VerticalCardSwiper
如果您需要此答案中的任何更多代码,请告诉我,然后我会添加它,但代码很多,因此访问存储库可能更容易。
关于iOS卡片滑动动画和 ScrollView ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40846403/
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里
print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上
动画/*INITIALIZEANANIMATION 初始化一个动画*-----------------------*/lv_anim_ta;lv_anim_init(&a);/*MANDATORYSETTINGS 必选设置*------------------*//*Setthe"animator"function 设置“动画”功能*/lv_anim_set_exec_cb(&a,(lv_anim_exec_xcb_t)lv_obj_set_x);/*Setthe"animator"function*/lv_anim_set_var(&a,obj);/*Lengthoftheanim
当我将IO::popen与不存在的命令一起使用时,我在屏幕上打印了一条错误消息:irb>IO.popen"fakefake"#=>#irb>(irb):1:commandnotfound:fakefake有什么方法可以捕获此错误,以便我可以在脚本中进行检查? 最佳答案 是:升级到ruby1.9。如果您在1.9中运行它,则会引发Errno::ENOENT,您将能够拯救它。(编辑)这是在1.8中的一种hackish方式:error=IO.pipe$stderr.reopenerror[1]pipe=IO.popen'qwe'#
当我尝试使用“套接字”库中的方法“read_nonblock”时出现以下错误IO::EAGAINWaitReadable:Resourcetemporarilyunavailable-readwouldblock但是当我通过终端上的IRB尝试时它工作正常如何让它读取缓冲区? 最佳答案 IgetthefollowingerrorwhenItrytousethemethod"read_nonblock"fromthe"socket"library当缓冲区中的数据未准备好时,这是预期的行为。由于异常IO::EAGAINWaitReadab
我需要将目录中的一堆文件上传到S3。由于上传所需的90%以上的时间都花在了等待http请求完成上,所以我想以某种方式同时执行其中的几个。Fibers能帮我解决这个问题吗?它们被描述为解决此类问题的一种方法,但我想不出在http调用阻塞时我可以做任何工作的任何方法。有什么方法可以在没有线程的情况下解决这个问题? 最佳答案 我没有使用1.9中的纤程,但是1.8.6中的常规线程可以解决这个问题。尝试使用队列http://ruby-doc.org/stdlib/libdoc/thread/rdoc/classes/Queue.html查看文
在ruby中...我有一个由外部进程创建的IO对象,我需要从中获取文件名。然而我似乎只能得到文件描述符(3),这对我来说不是很有用。有没有办法从此对象获取文件名甚至获取文件对象?我正在从通知程序中获取IO对象。所以这也可能是获取文件路径的一种方式? 最佳答案 关于howtogetathefilenameinC也有类似的问题,我将在这里以ruby的方式给出这个问题的答案。在Linux中获取文件名假设io是您的IO对象。以下代码为您提供了文件名。File.readlink("/proc/self/fd/#{io.fileno}")例